diff --git a/eng/helix.proj b/eng/helix.proj index 0d1b61f3902..2a9cc330859 100644 --- a/eng/helix.proj +++ b/eng/helix.proj @@ -12,7 +12,7 @@ true true - $(RepoRoot)/test/EFCore.SqlServer.FunctionalTests/*.csproj;$(RepoRoot)/test/EFCore.OData.FunctionalTests/*.csproj;$(RepoRoot)/test/EFCore.AspNet.SqlServer.FunctionalTests/*.csproj + $(RepoRoot)/test/EFCore.SqlServer.FunctionalTests/*.csproj;$(RepoRoot)/test/EFCore.SqlServer.HierarchyId.Tests/*.csproj;$(RepoRoot)/test/EFCore.OData.FunctionalTests/*.csproj;$(RepoRoot)/test/EFCore.AspNet.SqlServer.FunctionalTests/*.csproj diff --git a/src/EFCore.Abstractions/ChangeTracking/Internal/SortableBindingList.cs b/src/EFCore.Abstractions/ChangeTracking/Internal/SortableBindingList.cs index d51d999793f..d6eff18c86f 100644 --- a/src/EFCore.Abstractions/ChangeTracking/Internal/SortableBindingList.cs +++ b/src/EFCore.Abstractions/ChangeTracking/Internal/SortableBindingList.cs @@ -43,8 +43,8 @@ public SortableBindingList(List list) "ReflectionAnalysis", "IL2046", Justification = - "This method is an override, and the base method isn't annotated with RequiresUnreferencedCode. " + - "The entire type is marked with RequiresUnreferencedCode.")] + "This method is an override, and the base method isn't annotated with RequiresUnreferencedCode. " + + "The entire type is marked with RequiresUnreferencedCode.")] protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction) { if (PropertyComparer.CanSort(prop.PropertyType)) diff --git a/src/EFCore.Abstractions/ChangeTracking/ObservableCollectionListSource.cs b/src/EFCore.Abstractions/ChangeTracking/ObservableCollectionListSource.cs index 07458d41a35..0aa5deb7fb4 100644 --- a/src/EFCore.Abstractions/ChangeTracking/ObservableCollectionListSource.cs +++ b/src/EFCore.Abstractions/ChangeTracking/ObservableCollectionListSource.cs @@ -80,8 +80,8 @@ bool IListSource.ContainsListCollection "ReflectionAnalysis", "IL2046", Justification = - "This method is an interface implementation, and the interface method isn't annotated with RequiresUnreferencedCode. " + - "The entire type is marked with RequiresUnreferencedCode.")] + "This method is an interface implementation, and the interface method isn't annotated with RequiresUnreferencedCode. " + + "The entire type is marked with RequiresUnreferencedCode.")] IList IListSource.GetList() => _bindingList ??= this.ToBindingList(); } diff --git a/src/EFCore.SqlServer.Abstractions/EFCore.SqlServer.Abstractions.csproj b/src/EFCore.SqlServer.Abstractions/EFCore.SqlServer.Abstractions.csproj index ae4835d4b17..a62ea8f849a 100644 --- a/src/EFCore.SqlServer.Abstractions/EFCore.SqlServer.Abstractions.csproj +++ b/src/EFCore.SqlServer.Abstractions/EFCore.SqlServer.Abstractions.csproj @@ -1,16 +1,22 @@ - netstandard2.0 - EntityFrameworkCore.SqlServer.HierarchyId.Abstractions + netstandard2.1 + 3.6 + Microsoft.EntityFrameworkCore.SqlServer.Abstractions Microsoft.EntityFrameworkCore - Common abstractions for using hierarchyid with EF Core - true + Provides abstractions that are used by models in conjunction with the SQL Server EF Core provider + + Commonly Used Types: + Microsoft.EntityFrameworkCore.HierarchyId + true + $(PackageTags);SQL Server;HierarchyId + true - + diff --git a/src/EFCore.SqlServer.Abstractions/HierarchyId.cs b/src/EFCore.SqlServer.Abstractions/HierarchyId.cs index 67b4084361c..ec4f4867bb0 100644 --- a/src/EFCore.SqlServer.Abstractions/HierarchyId.cs +++ b/src/EFCore.SqlServer.Abstractions/HierarchyId.cs @@ -1,217 +1,216 @@ -using System; -using System.IO; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; using Microsoft.SqlServer.Types; -namespace Microsoft.EntityFrameworkCore +namespace Microsoft.EntityFrameworkCore; + +/// +/// Represents a position in a hierarchical structure, specifying depth and breadth. +/// +public class HierarchyId : IComparable { + private SqlHierarchyId _value; + + private HierarchyId(SqlHierarchyId value) + { + if (value.IsNull) + { + throw new ArgumentNullException(nameof(value)); + } + + _value = value; + } + + /// + /// Gets the root node of the hierarchy. + /// + /// The root node of the hierarchy. + public static HierarchyId GetRoot() + => new HierarchyId(SqlHierarchyId.GetRoot()); + + /// + /// Converts the canonical string representation of a node to a value. + /// + /// The string representation of a node. + /// A value. + [return: NotNullIfNotNull(nameof(input))] + public static HierarchyId? Parse(string? input) + => Wrap(SqlHierarchyId.Parse(input)); + /// - /// Represents a position in a hierarchical structure, specifying depth and breadth. + /// Reads a value from the specified reader. /// - public class HierarchyId : IComparable + /// The reader. + /// A value. + public static HierarchyId? Read(BinaryReader reader) { - private SqlHierarchyId _value; + var hid = new SqlHierarchyId(); + hid.Read(reader); + return Wrap(hid); + } - private HierarchyId(SqlHierarchyId value) - { - if (value.IsNull) - throw new ArgumentNullException(nameof(value)); + /// + /// Writes this value to the specified writer. + /// + /// The writer. + public void Write(BinaryWriter writer) + { + _value.Write(writer); + } - _value = value; - } + /// + public int CompareTo(object? obj) + => _value.CompareTo( + obj is HierarchyId or null + ? Unwrap((HierarchyId?)obj) + : obj); - /// - /// Gets the root node of the hierarchy. - /// - /// The root node of the hierarchy. - public static HierarchyId GetRoot() - => new HierarchyId(SqlHierarchyId.GetRoot()); - - /// - /// Converts the canonical string representation of a node to a value. - /// - /// The string representation of a node. - /// A value. - public static HierarchyId Parse(string input) - => Wrap(SqlHierarchyId.Parse(input)); - - /// - /// Reads a value from the specified reader. - /// - /// The reader. - /// A value. - public static HierarchyId Read(BinaryReader reader) - { - var hid = new SqlHierarchyId(); - hid.Read(reader); - return Wrap(hid); - } + /// + public override bool Equals(object? obj) + => _value.Equals( + obj is HierarchyId other + ? other._value + : obj); - /// - /// Writes this value to the specified writer. - /// - /// The writer. - public void Write(BinaryWriter writer) - { - _value.Write(writer); - } + /// + /// Gets the node levels up the hierarchical tree. + /// + /// The number of levels to ascend in the hierarchy. + /// A value representing the th ancestor of this node or null if is greater than . + /// is negative. + public HierarchyId GetAncestor(int n) + => Wrap(_value.GetAncestor(n))!; - /// - public int CompareTo(object obj) - => _value.CompareTo( - obj is HierarchyId other - ? other._value - : obj); - - /// - public override bool Equals(object obj) - => _value.Equals( - obj is HierarchyId other - ? other._value - : obj); - - /// - /// Gets the node levels up the hierarchical tree. - /// - /// The number of levels to ascend in the hierarchy. - /// A value representing the th ancestor of this node or null if is greater than . - /// is negative. - public HierarchyId GetAncestor(int n) - => Wrap(_value.GetAncestor(n)); - - /// - /// Gets the value of a descendant node that is greater than and less than . - /// - /// The lower bound. - /// The upper bound. - /// A value. - public HierarchyId GetDescendant(HierarchyId child1, HierarchyId child2) - => Wrap(_value.GetDescendant(Unwrap(child1), Unwrap(child2))); - - /// - public override int GetHashCode() - => _value.GetHashCode(); - - /// - /// Gets the level of this node in the hierarchical tree. - /// - /// The depth of this node. The root node is level 0. - public short GetLevel() - => _value.GetLevel().Value; - - /// - /// Gets a value representing the location of a new node that has a path from equal to the path from to this, effectively moving this to the new location. - /// - /// An ancestor of this node specifying the endpoint of the path segment to be moved. - /// The node that represents the new ancestor. - /// A value or null if or is null. - public HierarchyId GetReparentedValue(HierarchyId oldRoot, HierarchyId newRoot) - => Wrap(_value.GetReparentedValue(Unwrap(oldRoot), Unwrap(newRoot))); - - /// - /// Gets a value indicating whether this node is a descendant of . - /// - /// The parent to test against. - /// True if this node is in the sub-tree rooted at ; otherwise false. - public bool IsDescendantOf(HierarchyId parent) - { - if (parent == null) - return false; + /// + /// Gets the value of a descendant node that is greater than and less than . + /// + /// The lower bound. + /// The upper bound. + /// A value. + public HierarchyId GetDescendant(HierarchyId? child1, HierarchyId? child2) + => Wrap(_value.GetDescendant(Unwrap(child1), Unwrap(child2)))!; - return _value.IsDescendantOf(parent._value).Value; - } + /// + public override int GetHashCode() + => _value.GetHashCode(); - /// - public override string ToString() - => _value.ToString(); - - /// - /// Evaluates whether two nodes are equal. - /// - /// The first node to compare. - /// The second node to compare. - /// True if and are equal; otherwise, false. - public static bool operator ==(HierarchyId hid1, HierarchyId hid2) - { - var sh1 = Unwrap(hid1); - var sh2 = Unwrap(hid2); + /// + /// Gets the level of this node in the hierarchical tree. + /// + /// The depth of this node. The root node is level 0. + public short GetLevel() + => _value.GetLevel().Value; - return sh1.IsNull == sh2.IsNull && sh1.CompareTo(sh2) == 0; - } + /// + /// Gets a value representing the location of a new node that has a path from equal to the path from to this, effectively moving this to the new location. + /// + /// An ancestor of this node specifying the endpoint of the path segment to be moved. + /// The node that represents the new ancestor. + /// A value or null if or is null. + public HierarchyId? GetReparentedValue(HierarchyId? oldRoot, HierarchyId? newRoot) + => Wrap(_value.GetReparentedValue(Unwrap(oldRoot), Unwrap(newRoot))); - /// - /// Evaluates whether two nodes are unequal. - /// - /// The first node to compare. - /// The second node to compare. - /// True if and are unequal; otherwise, false. - public static bool operator !=(HierarchyId hid1, HierarchyId hid2) - { - var sh1 = Unwrap(hid1); - var sh2 = Unwrap(hid2); + /// + /// Gets a value indicating whether this node is a descendant of . + /// + /// The parent to test against. + /// True if this node is in the sub-tree rooted at ; otherwise false. + public bool IsDescendantOf(HierarchyId? parent) + => _value.IsDescendantOf(Unwrap(parent)).IsTrue; - return sh1.IsNull != sh2.IsNull || sh1.CompareTo(sh2) != 0; - } + /// + public override string ToString() + => _value.ToString(); - /// - /// Evaluates whether one node is less than another. - /// - /// The first node to compare. - /// The second node to compare. - /// True if is less than ; otherwise, false. - public static bool operator <(HierarchyId hid1, HierarchyId hid2) - { - var sh1 = Unwrap(hid1); - var sh2 = Unwrap(hid2); + /// + /// Evaluates whether two nodes are equal. + /// + /// The first node to compare. + /// The second node to compare. + /// True if and are equal; otherwise, false. + public static bool operator ==(HierarchyId? hid1, HierarchyId? hid2) + { + var sh1 = Unwrap(hid1); + var sh2 = Unwrap(hid2); - return !sh1.IsNull && !sh2.IsNull && sh1.CompareTo(sh2) < 0; - } + return sh1.IsNull == sh2.IsNull && sh1.CompareTo(sh2) == 0; + } - /// - /// Evaluates whether one node is greater than another. - /// - /// The first node to compare. - /// The second node to compare. - /// True if is greater than ; otherwise, false. - public static bool operator >(HierarchyId hid1, HierarchyId hid2) - { - var sh1 = Unwrap(hid1); - var sh2 = Unwrap(hid2); + /// + /// Evaluates whether two nodes are unequal. + /// + /// The first node to compare. + /// The second node to compare. + /// True if and are unequal; otherwise, false. + public static bool operator !=(HierarchyId? hid1, HierarchyId? hid2) + { + var sh1 = Unwrap(hid1); + var sh2 = Unwrap(hid2); - return !sh1.IsNull && !sh2.IsNull && sh1.CompareTo(sh2) > 0; - } + return sh1.IsNull != sh2.IsNull || sh1.CompareTo(sh2) != 0; + } - /// - /// Evaluates whether one node is less than or equal to another. - /// - /// The first node to compare. - /// The second node to compare. - /// True if is less than or equal to ; otherwise, false. - public static bool operator <=(HierarchyId hid1, HierarchyId hid2) - { - var sh1 = Unwrap(hid1); - var sh2 = Unwrap(hid2); + /// + /// Evaluates whether one node is less than another. + /// + /// The first node to compare. + /// The second node to compare. + /// True if is less than ; otherwise, false. + public static bool operator <(HierarchyId? hid1, HierarchyId? hid2) + { + var sh1 = Unwrap(hid1); + var sh2 = Unwrap(hid2); - return !sh1.IsNull && !sh2.IsNull && sh1.CompareTo(sh2) <= 0; - } + return !sh1.IsNull && !sh2.IsNull && sh1.CompareTo(sh2) < 0; + } - /// - /// Evaluates whether one node is greater than or equal to another. - /// - /// The first node to compare. - /// The second node to compare. - /// True if is greater than or equal to ; otherwise, false. - public static bool operator >=(HierarchyId hid1, HierarchyId hid2) - { - var sh1 = Unwrap(hid1); - var sh2 = Unwrap(hid2); + /// + /// Evaluates whether one node is greater than another. + /// + /// The first node to compare. + /// The second node to compare. + /// True if is greater than ; otherwise, false. + public static bool operator >(HierarchyId? hid1, HierarchyId? hid2) + { + var sh1 = Unwrap(hid1); + var sh2 = Unwrap(hid2); - return !sh1.IsNull && !sh2.IsNull && sh1.CompareTo(sh2) >= 0; - } + return !sh1.IsNull && !sh2.IsNull && sh1.CompareTo(sh2) > 0; + } - private static SqlHierarchyId Unwrap(HierarchyId value) - => value?._value ?? SqlHierarchyId.Null; + /// + /// Evaluates whether one node is less than or equal to another. + /// + /// The first node to compare. + /// The second node to compare. + /// True if is less than or equal to ; otherwise, false. + public static bool operator <=(HierarchyId? hid1, HierarchyId? hid2) + { + var sh1 = Unwrap(hid1); + var sh2 = Unwrap(hid2); - private static HierarchyId Wrap(SqlHierarchyId value) - => value.IsNull ? null : new HierarchyId(value); + return !sh1.IsNull && !sh2.IsNull && sh1.CompareTo(sh2) <= 0; } -} \ No newline at end of file + + /// + /// Evaluates whether one node is greater than or equal to another. + /// + /// The first node to compare. + /// The second node to compare. + /// True if is greater than or equal to ; otherwise, false. + public static bool operator >=(HierarchyId? hid1, HierarchyId? hid2) + { + var sh1 = Unwrap(hid1); + var sh2 = Unwrap(hid2); + + return !sh1.IsNull && !sh2.IsNull && sh1.CompareTo(sh2) >= 0; + } + + private static SqlHierarchyId Unwrap(HierarchyId? value) + => value?._value ?? SqlHierarchyId.Null; + + private static HierarchyId? Wrap(SqlHierarchyId value) + => value.IsNull ? null : new HierarchyId(value); +} diff --git a/src/EFCore.SqlServer.HierarchyId/Design/Internal/SqlServerHierarchyIdDesignTimeServices.cs b/src/EFCore.SqlServer.HierarchyId/Design/Internal/SqlServerHierarchyIdDesignTimeServices.cs new file mode 100644 index 00000000000..de4bb6922c3 --- /dev/null +++ b/src/EFCore.SqlServer.HierarchyId/Design/Internal/SqlServerHierarchyIdDesignTimeServices.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Scaffolding; +using Microsoft.EntityFrameworkCore.SqlServer.Scaffolding.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SqlServerHierarchyIdDesignTimeServices : IDesignTimeServices +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void ConfigureDesignTimeServices(IServiceCollection serviceCollection) + { + serviceCollection + .AddSingleton() + .AddSingleton(); + } +} diff --git a/src/EFCore.SqlServer.HierarchyId/Design/SqlServerHierarchyIdDesignTimeServices.cs b/src/EFCore.SqlServer.HierarchyId/Design/SqlServerHierarchyIdDesignTimeServices.cs deleted file mode 100644 index 9cfe4fbb710..00000000000 --- a/src/EFCore.SqlServer.HierarchyId/Design/SqlServerHierarchyIdDesignTimeServices.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.EntityFrameworkCore.Scaffolding; -using Microsoft.EntityFrameworkCore.SqlServer.Scaffolding; -using Microsoft.EntityFrameworkCore.SqlServer.Storage; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore.SqlServer.Design -{ - /// - /// Enables configuring design-time services. Tools will automatically discover implementations of this - /// interface that are in the startup assembly. - /// - public class SqlServerHierarchyIdDesignTimeServices : IDesignTimeServices - { - /// - /// Configures design-time services. Use this method to override the default design-time services with your - /// own implementations. - /// - /// The design-time service collection. - public virtual void ConfigureDesignTimeServices(IServiceCollection serviceCollection) - { - serviceCollection - .AddSingleton() - .AddSingleton(); - } - } -} diff --git a/src/EFCore.SqlServer.HierarchyId/EFCore.SqlServer.HierarchyId.csproj b/src/EFCore.SqlServer.HierarchyId/EFCore.SqlServer.HierarchyId.csproj index 5fe27346456..fad2f7a53c6 100644 --- a/src/EFCore.SqlServer.HierarchyId/EFCore.SqlServer.HierarchyId.csproj +++ b/src/EFCore.SqlServer.HierarchyId/EFCore.SqlServer.HierarchyId.csproj @@ -2,13 +2,25 @@ net6.0 - EntityFrameworkCore.SqlServer.HierarchyId + 3.6 + Microsoft.EntityFrameworkCore.SqlServer.HierarchyId Microsoft.EntityFrameworkCore.SqlServer Adds hierarchyid support to the SQL Server EF Core provider - true true + $(PackageTags);SQL Server;HierarchyId + true + + + + + + + + + + True @@ -17,26 +29,29 @@ - + + + - + + TextTemplatingFileGenerator + SqlServerHierarchyIdStrings.Designer.cs + Microsoft.EntityFrameworkCore.SqlServer.Internal + - - True - True - Resources.resx - + - - ResXFileCodeGenerator - Resources.Designer.cs - + + True + True + SqlServerHierarchyIdStrings.Designer.tt + diff --git a/src/EFCore.SqlServer.HierarchyId/Extensions/SqlServerHierarchyIdDbContextOptionsBuilderExtensions.cs b/src/EFCore.SqlServer.HierarchyId/Extensions/SqlServerHierarchyIdDbContextOptionsBuilderExtensions.cs index d4f926e8623..74c21901203 100644 --- a/src/EFCore.SqlServer.HierarchyId/Extensions/SqlServerHierarchyIdDbContextOptionsBuilderExtensions.cs +++ b/src/EFCore.SqlServer.HierarchyId/Extensions/SqlServerHierarchyIdDbContextOptionsBuilderExtensions.cs @@ -1,29 +1,31 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; + +namespace Microsoft.EntityFrameworkCore; + +/// +/// HierarchyId specific extension methods for . +/// +public static class SqlServerHierarchyIdDbContextOptionsBuilderExtensions { /// - /// HierarchyId specific extension methods for . + /// Enable HierarchyId mappings. /// - public static class SqlServerHierarchyIdDbContextOptionsBuilderExtensions + /// The builder being used to configure SQL Server. + /// The options builder so that further configuration can be chained. + public static SqlServerDbContextOptionsBuilder UseHierarchyId( + this SqlServerDbContextOptionsBuilder optionsBuilder) { - /// - /// Enable HierarchyId mappings. - /// - /// The builder being used to configure SQL Server. - /// The options builder so that further configuration can be chained. - public static SqlServerDbContextOptionsBuilder UseHierarchyId( - this SqlServerDbContextOptionsBuilder optionsBuilder) - { - var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)optionsBuilder).OptionsBuilder; + var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure)optionsBuilder).OptionsBuilder; - var extension = coreOptionsBuilder.Options.FindExtension() - ?? new SqlServerHierarchyIdOptionsExtension(); + var extension = coreOptionsBuilder.Options.FindExtension() + ?? new SqlServerHierarchyIdOptionsExtension(); - ((IDbContextOptionsBuilderInfrastructure)coreOptionsBuilder).AddOrUpdateExtension(extension); + ((IDbContextOptionsBuilderInfrastructure)coreOptionsBuilder).AddOrUpdateExtension(extension); - return optionsBuilder; - } + return optionsBuilder; } } diff --git a/src/EFCore.SqlServer.HierarchyId/Extensions/SqlServerHierarchyIdServiceCollectionExtensions.cs b/src/EFCore.SqlServer.HierarchyId/Extensions/SqlServerHierarchyIdServiceCollectionExtensions.cs index 77df6399117..5d4b3e2dc9a 100644 --- a/src/EFCore.SqlServer.HierarchyId/Extensions/SqlServerHierarchyIdServiceCollectionExtensions.cs +++ b/src/EFCore.SqlServer.HierarchyId/Extensions/SqlServerHierarchyIdServiceCollectionExtensions.cs @@ -1,29 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.SqlServer.Query.ExpressionTranslators; -using Microsoft.EntityFrameworkCore.SqlServer.Storage; +using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// EntityFrameworkCore.SqlServer.HierarchyId extension methods for . +/// +public static class SqlServerHierarchyIdServiceCollectionExtensions { /// - /// EntityFrameworkCore.SqlServer.HierarchyId extension methods for . + /// Adds the services required for HierarchyId support in the SQL Server provider for Entity Framework. /// - public static class SqlServerHierarchyIdServiceCollectionExtensions + /// The to add services to. + /// The same service collection so that multiple calls can be chained. + public static IServiceCollection AddEntityFrameworkSqlServerHierarchyId( + this IServiceCollection serviceCollection) { - /// - /// Adds the services required for HierarchyId support in the SQL Server provider for Entity Framework. - /// - /// The to add services to. - /// The same service collection so that multiple calls can be chained. - public static IServiceCollection AddEntityFrameworkSqlServerHierarchyId( - this IServiceCollection serviceCollection) - { - new EntityFrameworkRelationalServicesBuilder(serviceCollection) - .TryAdd() - .TryAdd(); + new EntityFrameworkRelationalServicesBuilder(serviceCollection) + .TryAdd() + .TryAdd(); - return serviceCollection; - } + return serviceCollection; } } diff --git a/src/EFCore.SqlServer.HierarchyId/Infrastructure/Internal/SqlServerHierarchyIdOptionsExtension.cs b/src/EFCore.SqlServer.HierarchyId/Infrastructure/Internal/SqlServerHierarchyIdOptionsExtension.cs new file mode 100644 index 00000000000..47905aa9f5c --- /dev/null +++ b/src/EFCore.SqlServer.HierarchyId/Infrastructure/Internal/SqlServerHierarchyIdOptionsExtension.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.SqlServer.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SqlServerHierarchyIdOptionsExtension : IDbContextOptionsExtension +{ + private DbContextOptionsExtensionInfo? _info; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public DbContextOptionsExtensionInfo Info + => _info ??= new ExtensionInfo(this); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void ApplyServices(IServiceCollection services) + { + services.AddEntityFrameworkSqlServerHierarchyId(); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void Validate(IDbContextOptions options) + { + var internalServiceProvider = options.FindExtension()?.InternalServiceProvider; + if (internalServiceProvider != null) + { + using (var scope = internalServiceProvider.CreateScope()) + { + if (scope.ServiceProvider.GetService>() + ?.Any(s => s is SqlServerHierarchyIdMethodCallTranslatorPlugin) + != true + || scope.ServiceProvider.GetService>() + ?.Any(s => s is SqlServerHierarchyIdTypeMappingSourcePlugin) + != true) + { + throw new InvalidOperationException(SqlServerHierarchyIdStrings.ServicesMissing); + } + } + } + } + + private sealed class ExtensionInfo : DbContextOptionsExtensionInfo + { + public ExtensionInfo(IDbContextOptionsExtension extension) + : base(extension) + { + } + + private new SqlServerHierarchyIdOptionsExtension Extension + => (SqlServerHierarchyIdOptionsExtension)base.Extension; + + public override bool IsDatabaseProvider + => false; + + public override int GetServiceProviderHashCode() + => 0; + + public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) + => other is ExtensionInfo; + + public override void PopulateDebugInfo(IDictionary debugInfo) + => debugInfo["SqlServer:" + nameof(SqlServerHierarchyIdDbContextOptionsBuilderExtensions.UseHierarchyId)] = "1"; + + public override string LogFragment + => "using HierarchyId "; + } +} diff --git a/src/EFCore.SqlServer.HierarchyId/Infrastructure/SqlServerHierarchyIdOptionsExtension.cs b/src/EFCore.SqlServer.HierarchyId/Infrastructure/SqlServerHierarchyIdOptionsExtension.cs deleted file mode 100644 index 1fdbd5265cc..00000000000 --- a/src/EFCore.SqlServer.HierarchyId/Infrastructure/SqlServerHierarchyIdOptionsExtension.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.SqlServer.Properties; -using Microsoft.EntityFrameworkCore.SqlServer.Query.ExpressionTranslators; -using Microsoft.EntityFrameworkCore.SqlServer.Storage; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore.SqlServer.Infrastructure -{ - internal class SqlServerHierarchyIdOptionsExtension : IDbContextOptionsExtension - { - private DbContextOptionsExtensionInfo _info; - - public DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this); - - public virtual void ApplyServices(IServiceCollection services) - { - services.AddEntityFrameworkSqlServerHierarchyId(); - } - - public virtual void Validate(IDbContextOptions options) - { - var internalServiceProvider = options.FindExtension()?.InternalServiceProvider; - if (internalServiceProvider != null) - { - using (var scope = internalServiceProvider.CreateScope()) - { - if (scope.ServiceProvider.GetService>() - ?.Any(s => s is SqlServerHierarchyIdMethodCallTranslatorPlugin) != true || - scope.ServiceProvider.GetService>() - ?.Any(s => s is SqlServerHierarchyIdTypeMappingSourcePlugin) != true) - { - throw new InvalidOperationException(Resources.ServicesMissing); - } - } - } - } - - private sealed class ExtensionInfo : DbContextOptionsExtensionInfo - { - public ExtensionInfo(IDbContextOptionsExtension extension) - : base(extension) - { - } - - private new SqlServerHierarchyIdOptionsExtension Extension - => (SqlServerHierarchyIdOptionsExtension)base.Extension; - - public override bool IsDatabaseProvider => false; - - public override int GetServiceProviderHashCode() => 0; - - public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) - => other is ExtensionInfo; - - public override void PopulateDebugInfo(IDictionary debugInfo) - => debugInfo["SqlServer:" + nameof(SqlServerHierarchyIdDbContextOptionsBuilderExtensions.UseHierarchyId)] = "1"; - - public override string LogFragment => "using HierarchyId "; - } - } -} diff --git a/src/EFCore.SqlServer.HierarchyId/Properties/InternalsVisibleTo.cs b/src/EFCore.SqlServer.HierarchyId/Properties/InternalsVisibleTo.cs deleted file mode 100644 index 5fd03cebc42..00000000000 --- a/src/EFCore.SqlServer.HierarchyId/Properties/InternalsVisibleTo.cs +++ /dev/null @@ -1,4 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo( - "EntityFrameworkCore.SqlServer.HierarchyId.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001006d92138307b5e251bf4918cf751dc83489f4b2d70ac15b04110fd1f78491fe93719d0cd464d103a95fb1c3b1cb21ce0033c94c6f52b325d36360736dea7571bd1074cb2c937cf4fc54526ceb44271c4f44753dbeb5d9b364e4dc57a8988542d3a7edb6575bc35ce7670612bd8f00f2c6899f3e74bd563810fa45f4c5c8b51cd3")] diff --git a/src/EFCore.SqlServer.HierarchyId/Properties/Resources.Designer.cs b/src/EFCore.SqlServer.HierarchyId/Properties/Resources.Designer.cs deleted file mode 100644 index 2dd653ff2b1..00000000000 --- a/src/EFCore.SqlServer.HierarchyId/Properties/Resources.Designer.cs +++ /dev/null @@ -1,72 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.EntityFrameworkCore.SqlServer.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.EntityFrameworkCore.SqlServer.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to UseHierarchyId requires AddEntityFrameworkSqlServerHierarchyId to be called on the internal service provider used.. - /// - internal static string ServicesMissing { - get { - return ResourceManager.GetString("ServicesMissing", resourceCulture); - } - } - } -} diff --git a/src/EFCore.SqlServer.HierarchyId/Properties/SqlServerHierarchyIdStrings.Designer.cs b/src/EFCore.SqlServer.HierarchyId/Properties/SqlServerHierarchyIdStrings.Designer.cs new file mode 100644 index 00000000000..1d2fc122f36 --- /dev/null +++ b/src/EFCore.SqlServer.HierarchyId/Properties/SqlServerHierarchyIdStrings.Designer.cs @@ -0,0 +1,40 @@ +// + +using System; +using System.Reflection; +using System.Resources; + +#nullable enable + +namespace Microsoft.EntityFrameworkCore.SqlServer.Internal +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static class SqlServerHierarchyIdStrings + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.EntityFrameworkCore.SqlServer.Properties.SqlServerHierarchyIdStrings", typeof(SqlServerHierarchyIdStrings).Assembly); + + /// + /// UseHierarchyId requires AddEntityFrameworkSqlServerHierarchyId to be called on the internal service provider used. + /// + public static string ServicesMissing + => GetString("ServicesMissing"); + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name)!; + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + + return value; + } + } +} + diff --git a/src/EFCore.SqlServer.HierarchyId/Properties/SqlServerHierarchyIdStrings.Designer.tt b/src/EFCore.SqlServer.HierarchyId/Properties/SqlServerHierarchyIdStrings.Designer.tt new file mode 100644 index 00000000000..3946db3154a --- /dev/null +++ b/src/EFCore.SqlServer.HierarchyId/Properties/SqlServerHierarchyIdStrings.Designer.tt @@ -0,0 +1,7 @@ +<# + Session["ResourceFile"] = "SqlServerHierarchyIdStrings.resx"; + Session["ResourceNamespace"] = "Microsoft.EntityFrameworkCore.SqlServer.Properties"; + Session["LoggingDefinitionsClass"] = "SqlServerHierarchyIdLoggingDefinitions"; + Session["NoDiagnostics"] = true; +#> +<#@ include file="..\..\..\tools\Resources.tt" #> \ No newline at end of file diff --git a/src/EFCore.SqlServer.HierarchyId/Properties/Resources.resx b/src/EFCore.SqlServer.HierarchyId/Properties/SqlServerHierarchyIdStrings.resx similarity index 100% rename from src/EFCore.SqlServer.HierarchyId/Properties/Resources.resx rename to src/EFCore.SqlServer.HierarchyId/Properties/SqlServerHierarchyIdStrings.resx diff --git a/src/EFCore.SqlServer.HierarchyId/Query/ExpressionTranslators/SqlServerHierarchyIdMethodCallTranslatorPlugin.cs b/src/EFCore.SqlServer.HierarchyId/Query/ExpressionTranslators/SqlServerHierarchyIdMethodCallTranslatorPlugin.cs deleted file mode 100644 index 92ff81eaefa..00000000000 --- a/src/EFCore.SqlServer.HierarchyId/Query/ExpressionTranslators/SqlServerHierarchyIdMethodCallTranslatorPlugin.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Storage; - -namespace Microsoft.EntityFrameworkCore.SqlServer.Query.ExpressionTranslators -{ - internal class SqlServerHierarchyIdMethodCallTranslatorPlugin : IMethodCallTranslatorPlugin - { - public SqlServerHierarchyIdMethodCallTranslatorPlugin( - IRelationalTypeMappingSource typeMappingSource, - ISqlExpressionFactory sqlExpressionFactory) - { - Translators = new IMethodCallTranslator[] - { - new SqlServerHierarchyIdMethodTranslator(typeMappingSource, sqlExpressionFactory) - }; - } - - public virtual IEnumerable Translators { get; } - } -} diff --git a/src/EFCore.SqlServer.HierarchyId/Query/ExpressionTranslators/SqlServerHierarchyIdMethodTranslator.cs b/src/EFCore.SqlServer.HierarchyId/Query/ExpressionTranslators/SqlServerHierarchyIdMethodTranslator.cs deleted file mode 100644 index 0b2af7439a6..00000000000 --- a/src/EFCore.SqlServer.HierarchyId/Query/ExpressionTranslators/SqlServerHierarchyIdMethodTranslator.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -using Microsoft.EntityFrameworkCore.SqlServer.Storage; -using Microsoft.EntityFrameworkCore.Storage; - -namespace Microsoft.EntityFrameworkCore.SqlServer.Query.ExpressionTranslators -{ - internal class SqlServerHierarchyIdMethodTranslator : IMethodCallTranslator - { - private static readonly IDictionary _methodToFunctionName = new Dictionary - { - // instance methods - { typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.GetAncestor), new[] { typeof(int) }), "GetAncestor" }, - { typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.GetDescendant), new[] { typeof(HierarchyId), typeof(HierarchyId) }), "GetDescendant" }, - { typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.GetLevel), Type.EmptyTypes), "GetLevel" }, - { typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.GetReparentedValue), new[] { typeof(HierarchyId), typeof(HierarchyId) }), "GetReparentedValue" }, - { typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.IsDescendantOf), new[] { typeof(HierarchyId) }), "IsDescendantOf" }, - { typeof(object).GetRuntimeMethod(nameof(HierarchyId.ToString), Type.EmptyTypes), "ToString" }, - - // static methods - { typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.GetRoot), Type.EmptyTypes), "hierarchyid::GetRoot" }, - { typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.Parse), new[] { typeof(string) }), "hierarchyid::Parse" }, - }; - - private readonly IRelationalTypeMappingSource _typeMappingSource; - private readonly ISqlExpressionFactory _sqlExpressionFactory; - - public SqlServerHierarchyIdMethodTranslator( - IRelationalTypeMappingSource typeMappingSource, - ISqlExpressionFactory sqlExpressionFactory) - { - _typeMappingSource = typeMappingSource; - _sqlExpressionFactory = sqlExpressionFactory; - } - - public SqlExpression Translate( - SqlExpression instance, - MethodInfo method, - IReadOnlyList arguments, - IDiagnosticsLogger logger) - { - // instance is null for static methods like Parse - const string storeType = SqlServerHierarchyIdTypeMappingSourcePlugin.SqlServerTypeName; - var callingType = instance?.Type ?? method.DeclaringType; - if (typeof(HierarchyId).IsAssignableFrom(callingType) - && _methodToFunctionName.TryGetValue(method, out var functionName)) - { - var typeMappedArguments = new List(); - foreach (var argument in arguments) - { - var argumentTypeMapping = typeof(HierarchyId).IsAssignableFrom(argument.Type) - ? _typeMappingSource.FindMapping(argument.Type, storeType) - : _typeMappingSource.FindMapping(argument.Type); - var mappedArgument = _sqlExpressionFactory.ApplyTypeMapping(argument, argumentTypeMapping); - typeMappedArguments.Add(mappedArgument); - } - - var resultTypeMapping = typeof(HierarchyId).IsAssignableFrom(method.ReturnType) - ? _typeMappingSource.FindMapping(method.ReturnType, storeType) - : _typeMappingSource.FindMapping(method.ReturnType); - - - if (instance != null) - { - var instanceMapping = _typeMappingSource.FindMapping(instance.Type, storeType); - instance = _sqlExpressionFactory.ApplyTypeMapping(instance, instanceMapping); - - return _sqlExpressionFactory.Function( - instance, - functionName, - simplify(arguments), - nullable: true, - instancePropagatesNullability: true, - argumentsPropagateNullability: arguments.Select(a => true), - method.ReturnType, - resultTypeMapping); - } - - return _sqlExpressionFactory.Function( - functionName, - simplify(arguments), - nullable: true, - argumentsPropagateNullability: arguments.Select(a => true), - method.ReturnType, - resultTypeMapping); - } - - return null; - } - - private IEnumerable simplify(IEnumerable arguments) - { - foreach (var argument in arguments) - { - if (argument is SqlConstantExpression constant - && constant.Value is HierarchyId hierarchyId) - { - yield return _sqlExpressionFactory.Fragment($"'{hierarchyId}'"); - } - else - { - yield return argument; - } - } - } - } -} diff --git a/src/EFCore.SqlServer.HierarchyId/Query/Internal/SqlServerHierarchyIdMethodCallTranslatorPlugin.cs b/src/EFCore.SqlServer.HierarchyId/Query/Internal/SqlServerHierarchyIdMethodCallTranslatorPlugin.cs new file mode 100644 index 00000000000..712bb58671d --- /dev/null +++ b/src/EFCore.SqlServer.HierarchyId/Query/Internal/SqlServerHierarchyIdMethodCallTranslatorPlugin.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SqlServerHierarchyIdMethodCallTranslatorPlugin : IMethodCallTranslatorPlugin +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SqlServerHierarchyIdMethodCallTranslatorPlugin( + IRelationalTypeMappingSource typeMappingSource, + ISqlExpressionFactory sqlExpressionFactory) + { + Translators = new IMethodCallTranslator[] { new SqlServerHierarchyIdMethodTranslator(typeMappingSource, sqlExpressionFactory) }; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable Translators { get; } +} diff --git a/src/EFCore.SqlServer.HierarchyId/Query/Internal/SqlServerHierarchyIdMethodTranslator.cs b/src/EFCore.SqlServer.HierarchyId/Query/Internal/SqlServerHierarchyIdMethodTranslator.cs new file mode 100644 index 00000000000..3c697523fac --- /dev/null +++ b/src/EFCore.SqlServer.HierarchyId/Query/Internal/SqlServerHierarchyIdMethodTranslator.cs @@ -0,0 +1,133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SqlServerHierarchyIdMethodTranslator : IMethodCallTranslator +{ + private static readonly IDictionary _methodToFunctionName = new Dictionary + { + // instance methods + { typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.GetAncestor), new[] { typeof(int) })!, "GetAncestor" }, + { + typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.GetDescendant), new[] { typeof(HierarchyId), typeof(HierarchyId) })!, + "GetDescendant" + }, + { typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.GetLevel), Type.EmptyTypes)!, "GetLevel" }, + { + typeof(HierarchyId).GetRuntimeMethod( + nameof(HierarchyId.GetReparentedValue), new[] { typeof(HierarchyId), typeof(HierarchyId) })!, + "GetReparentedValue" + }, + { typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.IsDescendantOf), new[] { typeof(HierarchyId) })!, "IsDescendantOf" }, + { typeof(object).GetRuntimeMethod(nameof(HierarchyId.ToString), Type.EmptyTypes)!, "ToString" }, + + // static methods + { typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.GetRoot), Type.EmptyTypes)!, "hierarchyid::GetRoot" }, + { typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.Parse), new[] { typeof(string) })!, "hierarchyid::Parse" }, + }; + + private readonly IRelationalTypeMappingSource _typeMappingSource; + private readonly ISqlExpressionFactory _sqlExpressionFactory; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SqlServerHierarchyIdMethodTranslator( + IRelationalTypeMappingSource typeMappingSource, + ISqlExpressionFactory sqlExpressionFactory) + { + _typeMappingSource = typeMappingSource; + _sqlExpressionFactory = sqlExpressionFactory; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SqlExpression? Translate( + SqlExpression? instance, + MethodInfo method, + IReadOnlyList arguments, + IDiagnosticsLogger logger) + { + // instance is null for static methods like Parse + const string storeType = SqlServerHierarchyIdTypeMappingSourcePlugin.SqlServerTypeName; + var callingType = instance?.Type ?? method.DeclaringType; + if (typeof(HierarchyId).IsAssignableFrom(callingType) + && _methodToFunctionName.TryGetValue(method, out var functionName)) + { + var typeMappedArguments = new List(); + foreach (var argument in arguments) + { + var argumentTypeMapping = typeof(HierarchyId).IsAssignableFrom(argument.Type) + ? _typeMappingSource.FindMapping(argument.Type, storeType) + : _typeMappingSource.FindMapping(argument.Type); + var mappedArgument = _sqlExpressionFactory.ApplyTypeMapping(argument, argumentTypeMapping); + typeMappedArguments.Add(mappedArgument); + } + + var resultTypeMapping = typeof(HierarchyId).IsAssignableFrom(method.ReturnType) + ? _typeMappingSource.FindMapping(method.ReturnType, storeType) + : _typeMappingSource.FindMapping(method.ReturnType); + + if (instance != null) + { + var instanceMapping = _typeMappingSource.FindMapping(instance.Type, storeType); + instance = _sqlExpressionFactory.ApplyTypeMapping(instance, instanceMapping); + + return _sqlExpressionFactory.Function( + instance, + functionName, + Simplify(arguments), + nullable: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: arguments.Select(a => true), + method.ReturnType, + resultTypeMapping); + } + + return _sqlExpressionFactory.Function( + functionName, + Simplify(arguments), + nullable: true, + argumentsPropagateNullability: arguments.Select(a => true), + method.ReturnType, + resultTypeMapping); + } + + return null; + } + + private IEnumerable Simplify(IEnumerable arguments) + { + foreach (var argument in arguments) + { + if (argument is SqlConstantExpression constant + && constant.Value is HierarchyId hierarchyId) + { + yield return _sqlExpressionFactory.Fragment($"'{hierarchyId}'"); + } + else + { + yield return argument; + } + } + } +} diff --git a/src/EFCore.SqlServer.HierarchyId/Scaffolding/Internal/SqlServerHierarchyIdCodeGeneratorPlugin.cs b/src/EFCore.SqlServer.HierarchyId/Scaffolding/Internal/SqlServerHierarchyIdCodeGeneratorPlugin.cs new file mode 100644 index 00000000000..4d4667064d5 --- /dev/null +++ b/src/EFCore.SqlServer.HierarchyId/Scaffolding/Internal/SqlServerHierarchyIdCodeGeneratorPlugin.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Scaffolding; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Scaffolding.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SqlServerHierarchyIdCodeGeneratorPlugin : ProviderCodeGeneratorPlugin +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override MethodCallCodeFragment GenerateProviderOptions() + { + return new MethodCallCodeFragment( + typeof(SqlServerHierarchyIdDbContextOptionsBuilderExtensions).GetRuntimeMethod( + nameof(SqlServerHierarchyIdDbContextOptionsBuilderExtensions.UseHierarchyId), + new[] { typeof(SqlServerDbContextOptionsBuilder) })!); + } +} diff --git a/src/EFCore.SqlServer.HierarchyId/Scaffolding/SqlServerHierarchyIdCodeGeneratorPlugin.cs b/src/EFCore.SqlServer.HierarchyId/Scaffolding/SqlServerHierarchyIdCodeGeneratorPlugin.cs deleted file mode 100644 index 899f13290b5..00000000000 --- a/src/EFCore.SqlServer.HierarchyId/Scaffolding/SqlServerHierarchyIdCodeGeneratorPlugin.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Reflection; -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Scaffolding; - -namespace Microsoft.EntityFrameworkCore.SqlServer.Scaffolding -{ - internal class SqlServerHierarchyIdCodeGeneratorPlugin : ProviderCodeGeneratorPlugin - { - public override MethodCallCodeFragment GenerateProviderOptions() - { - return new MethodCallCodeFragment( - typeof(SqlServerHierarchyIdDbContextOptionsBuilderExtensions).GetRuntimeMethod( - nameof(SqlServerHierarchyIdDbContextOptionsBuilderExtensions.UseHierarchyId), - new[] { typeof(SqlServerDbContextOptionsBuilder) })); - } - } -} diff --git a/src/EFCore.SqlServer.HierarchyId/Storage/Internal/SqlServerHierarchyIdTypeMapping.cs b/src/EFCore.SqlServer.HierarchyId/Storage/Internal/SqlServerHierarchyIdTypeMapping.cs new file mode 100644 index 00000000000..d346691b106 --- /dev/null +++ b/src/EFCore.SqlServer.HierarchyId/Storage/Internal/SqlServerHierarchyIdTypeMapping.cs @@ -0,0 +1,219 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data; +using System.Data.Common; +using System.Data.SqlTypes; +using System.Linq.Expressions; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.SqlServer.Storage.ValueConversion.Internal; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SqlServerHierarchyIdTypeMapping : RelationalTypeMapping +{ + private static readonly MethodInfo _getSqlBytes + = typeof(SqlDataReader).GetRuntimeMethod(nameof(SqlDataReader.GetSqlBytes), new[] { typeof(int) })!; + + private static readonly MethodInfo _parseHierarchyId + = typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.Parse), new[] { typeof(string) })!; + + private static readonly SqlServerHierarchyIdValueConverter _valueConverter = new(); + + private static Action? _sqlDbTypeSetter; + private static Action? _udtTypeNameSetter; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SqlServerHierarchyIdTypeMapping(string storeType, Type clrType) + : base(CreateRelationalTypeMappingParameters(storeType, clrType)) + { + } + + private static RelationalTypeMappingParameters CreateRelationalTypeMappingParameters(string storeType, Type clrType) + { + return new RelationalTypeMappingParameters( + new CoreTypeMappingParameters( + clrType: clrType, + converter: null //this gets the generatecodeliteral to run + ), + storeType); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + // needed to implement Clone + protected SqlServerHierarchyIdTypeMapping(RelationalTypeMappingParameters parameters) + : base(parameters) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + { + return new SqlServerHierarchyIdTypeMapping(parameters); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override void ConfigureParameter(DbParameter parameter) + { + var type = parameter.GetType(); + LazyInitializer.EnsureInitialized(ref _sqlDbTypeSetter, () => CreateSqlDbTypeAccessor(type)); + LazyInitializer.EnsureInitialized(ref _udtTypeNameSetter, () => CreateUdtTypeNameAccessor(type)); + + if (parameter.Value == DBNull.Value) + { + parameter.Value = SqlBytes.Null; + } + + _sqlDbTypeSetter(parameter, SqlDbType.Udt); + _udtTypeNameSetter(parameter, StoreType); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override MethodInfo GetDataReaderMethod() + { + return _getSqlBytes; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override Expression GenerateCodeLiteral(object value) + { + return Expression.Call( + _parseHierarchyId, + Expression.Constant(value.ToString(), typeof(string)) + ); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override string GenerateNonNullSqlLiteral(object value) + { + //this appears to only be called when using the update-database + //command, and the value is already a hierarchyid + return $"'{value}'"; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override DbParameter CreateParameter( + DbCommand command, + string name, + object? value, + bool? nullable = null, + ParameterDirection direction = ParameterDirection.Input) + { + var parameter = command.CreateParameter(); + parameter.Direction = ParameterDirection.Input; + parameter.ParameterName = name; + + if (Converter != null) + { + value = Converter.ConvertToProvider(value); + } + + parameter.Value = value is null + ? DBNull.Value + : _valueConverter.ConvertToProvider(value); + + if (nullable.HasValue) + { + parameter.IsNullable = nullable.Value; + } + + ConfigureParameter(parameter); + + return parameter; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override Expression CustomizeDataReaderExpression(Expression expression) + { + if (expression.Type != _valueConverter.ProviderClrType) + { + expression = Expression.Convert(expression, _valueConverter.ProviderClrType); + } + + return ReplacingExpressionVisitor.Replace( + _valueConverter.ConvertFromProviderExpression.Parameters.Single(), + expression, + _valueConverter.ConvertFromProviderExpression.Body); + } + + private static Action CreateSqlDbTypeAccessor(Type paramType) + { + var paramParam = Expression.Parameter(typeof(DbParameter), "parameter"); + var valueParam = Expression.Parameter(typeof(SqlDbType), "value"); + + return Expression.Lambda>( + Expression.Call( + Expression.Convert(paramParam, paramType), + paramType.GetProperty("SqlDbType")!.SetMethod!, + valueParam), + paramParam, + valueParam).Compile(); + } + + private static Action CreateUdtTypeNameAccessor(Type paramType) + { + var paramParam = Expression.Parameter(typeof(DbParameter), "parameter"); + var valueParam = Expression.Parameter(typeof(string), "value"); + + return Expression.Lambda>( + Expression.Call( + Expression.Convert(paramParam, paramType), + paramType.GetProperty("UdtTypeName")!.SetMethod!, + valueParam), + paramParam, + valueParam).Compile(); + } +} diff --git a/src/EFCore.SqlServer.HierarchyId/Storage/Internal/SqlServerHierarchyIdTypeMappingSourcePlugin.cs b/src/EFCore.SqlServer.HierarchyId/Storage/Internal/SqlServerHierarchyIdTypeMappingSourcePlugin.cs new file mode 100644 index 00000000000..b2fb980a0b7 --- /dev/null +++ b/src/EFCore.SqlServer.HierarchyId/Storage/Internal/SqlServerHierarchyIdTypeMappingSourcePlugin.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Storage; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SqlServerHierarchyIdTypeMappingSourcePlugin : IRelationalTypeMappingSourcePlugin +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public const string SqlServerTypeName = "hierarchyid"; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual RelationalTypeMapping? FindMapping(in RelationalTypeMappingInfo mappingInfo) + { + var clrType = mappingInfo.ClrType; + var storeTypeName = mappingInfo.StoreTypeName; + + return typeof(HierarchyId).IsAssignableFrom(clrType) + || SqlServerTypeName.Equals(storeTypeName, StringComparison.OrdinalIgnoreCase) + ? new SqlServerHierarchyIdTypeMapping(SqlServerTypeName, clrType ?? typeof(HierarchyId)) + : null; + } +} diff --git a/src/EFCore.SqlServer.HierarchyId/Storage/SqlServerHierarchyIdTypeMapping.cs b/src/EFCore.SqlServer.HierarchyId/Storage/SqlServerHierarchyIdTypeMapping.cs deleted file mode 100644 index 4c08f5d2264..00000000000 --- a/src/EFCore.SqlServer.HierarchyId/Storage/SqlServerHierarchyIdTypeMapping.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.Data; -using System.Data.Common; -using System.Data.SqlTypes; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Threading; -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Storage; - -namespace Microsoft.EntityFrameworkCore.SqlServer.Storage -{ - internal class SqlServerHierarchyIdTypeMapping : RelationalTypeMapping - { - private static readonly MethodInfo _getSqlBytes - = typeof(SqlDataReader).GetRuntimeMethod(nameof(SqlDataReader.GetSqlBytes), new[] { typeof(int) }); - - private static readonly MethodInfo _parseHierarchyId - = typeof(HierarchyId).GetRuntimeMethod(nameof(HierarchyId.Parse), new[] { typeof(string) }); - - private static readonly SqlServerHierarchyIdValueConverter _valueConverter = new SqlServerHierarchyIdValueConverter(); - - private static Action _sqlDbTypeSetter; - private static Action _udtTypeNameSetter; - - public SqlServerHierarchyIdTypeMapping(string storeType, Type clrType) - : base(CreateRelationalTypeMappingParameters(storeType, clrType)) - { - } - - private static RelationalTypeMappingParameters CreateRelationalTypeMappingParameters(string storeType, Type clrType) - { - return new RelationalTypeMappingParameters( - new CoreTypeMappingParameters( - clrType: clrType, - converter: null //this gets the generatecodeliteral to run - ), - storeType); - } - - // needed to implement Clone - protected SqlServerHierarchyIdTypeMapping(RelationalTypeMappingParameters parameters) - : base(parameters) - { - } - - protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) - { - return new SqlServerHierarchyIdTypeMapping(parameters); - } - - protected override void ConfigureParameter(DbParameter parameter) - { - var type = parameter.GetType(); - LazyInitializer.EnsureInitialized(ref _sqlDbTypeSetter, () => CreateSqlDbTypeAccessor(type)); - LazyInitializer.EnsureInitialized(ref _udtTypeNameSetter, () => CreateUdtTypeNameAccessor(type)); - - if (parameter.Value == DBNull.Value) - { - parameter.Value = SqlBytes.Null; - } - - _sqlDbTypeSetter(parameter, SqlDbType.Udt); - _udtTypeNameSetter(parameter, StoreType); - } - - public override MethodInfo GetDataReaderMethod() - { - return _getSqlBytes; - } - - public override Expression GenerateCodeLiteral(object value) - { - return Expression.Call( - _parseHierarchyId, - Expression.Constant(value.ToString(), typeof(string)) - ); - } - - protected override string GenerateNonNullSqlLiteral(object value) - { - //this appears to only be called when using the update-database - //command, and the value is already a hierarchyid - return $"'{value}'"; - } - - public override DbParameter CreateParameter(DbCommand command, string name, object value, bool? nullable = null, ParameterDirection direction = ParameterDirection.Input) - { - var parameter = command.CreateParameter(); - parameter.Direction = ParameterDirection.Input; - parameter.ParameterName = name; - - if (Converter != null) - { - value = Converter.ConvertToProvider(value); - } - - parameter.Value = value is null - ? DBNull.Value - : _valueConverter.ConvertToProvider(value); - - if (nullable.HasValue) - { - parameter.IsNullable = nullable.Value; - } - - ConfigureParameter(parameter); - - return parameter; - } - - public override Expression CustomizeDataReaderExpression(Expression expression) - { - if (expression.Type != _valueConverter.ProviderClrType) - { - expression = Expression.Convert(expression, _valueConverter.ProviderClrType); - } - - return ReplacingExpressionVisitor.Replace( - _valueConverter.ConvertFromProviderExpression.Parameters.Single(), - expression, - _valueConverter.ConvertFromProviderExpression.Body); - } - - private static Action CreateSqlDbTypeAccessor(Type paramType) - { - var paramParam = Expression.Parameter(typeof(DbParameter), "parameter"); - var valueParam = Expression.Parameter(typeof(SqlDbType), "value"); - - return Expression.Lambda>( - Expression.Call( - Expression.Convert(paramParam, paramType), - paramType.GetProperty("SqlDbType").SetMethod, - valueParam), - paramParam, - valueParam).Compile(); - } - - private static Action CreateUdtTypeNameAccessor(Type paramType) - { - var paramParam = Expression.Parameter(typeof(DbParameter), "parameter"); - var valueParam = Expression.Parameter(typeof(string), "value"); - - return Expression.Lambda>( - Expression.Call( - Expression.Convert(paramParam, paramType), - paramType.GetProperty("UdtTypeName").SetMethod, - valueParam), - paramParam, - valueParam).Compile(); - } - } -} diff --git a/src/EFCore.SqlServer.HierarchyId/Storage/SqlServerHierarchyIdTypeMappingSourcePlugin.cs b/src/EFCore.SqlServer.HierarchyId/Storage/SqlServerHierarchyIdTypeMappingSourcePlugin.cs deleted file mode 100644 index 10e54dd2189..00000000000 --- a/src/EFCore.SqlServer.HierarchyId/Storage/SqlServerHierarchyIdTypeMappingSourcePlugin.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Storage; - -namespace Microsoft.EntityFrameworkCore.SqlServer.Storage -{ - internal class SqlServerHierarchyIdTypeMappingSourcePlugin : IRelationalTypeMappingSourcePlugin - { - public const string SqlServerTypeName = "hierarchyid"; - - public virtual RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo) - { - var clrType = mappingInfo.ClrType; - var storeTypeName = mappingInfo.StoreTypeName; - - return typeof(HierarchyId).IsAssignableFrom(clrType) - || SqlServerTypeName.Equals(storeTypeName, StringComparison.OrdinalIgnoreCase) - ? new SqlServerHierarchyIdTypeMapping(SqlServerTypeName, clrType ?? typeof(HierarchyId)) - : null; - } - } -} diff --git a/src/EFCore.SqlServer.HierarchyId/Storage/SqlServerHierarchyIdValueConverter.cs b/src/EFCore.SqlServer.HierarchyId/Storage/SqlServerHierarchyIdValueConverter.cs deleted file mode 100644 index cd5593c4f72..00000000000 --- a/src/EFCore.SqlServer.HierarchyId/Storage/SqlServerHierarchyIdValueConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Data.SqlTypes; -using System.IO; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Microsoft.EntityFrameworkCore.SqlServer.Storage -{ - internal class SqlServerHierarchyIdValueConverter : ValueConverter - { - public SqlServerHierarchyIdValueConverter() - : base(h => toProvider(h), b => fromProvider(b)) - { - } - - private static SqlBytes toProvider(HierarchyId hid) - { - using (var memory = new MemoryStream()) - using (var writer = new BinaryWriter(memory)) - { - hid.Write(writer); - return new SqlBytes(memory.ToArray()); - } - } - - private static HierarchyId fromProvider(SqlBytes bytes) - { - using (var memory = new MemoryStream(bytes.Value)) - using (var reader = new BinaryReader(memory)) - { - return HierarchyId.Read(reader); - } - } - } -} diff --git a/src/EFCore.SqlServer.HierarchyId/Storage/ValueConversion/Internal/SqlServerHierarchyIdValueConverter.cs b/src/EFCore.SqlServer.HierarchyId/Storage/ValueConversion/Internal/SqlServerHierarchyIdValueConverter.cs new file mode 100644 index 00000000000..7edd8eabddb --- /dev/null +++ b/src/EFCore.SqlServer.HierarchyId/Storage/ValueConversion/Internal/SqlServerHierarchyIdValueConverter.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Data.SqlTypes; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.ValueConversion.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class SqlServerHierarchyIdValueConverter : ValueConverter +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SqlServerHierarchyIdValueConverter() + : base(h => ToProvider(h), b => FromProvider(b)) + { + } + + private static SqlBytes ToProvider(HierarchyId hid) + { + using var memory = new MemoryStream(); + using var writer = new BinaryWriter(memory); + + hid.Write(writer); + return new SqlBytes(memory.ToArray()); + } + + private static HierarchyId FromProvider(SqlBytes bytes) + { + using var memory = new MemoryStream(bytes.Value); + using var reader = new BinaryReader(memory); + + return HierarchyId.Read(reader)!; + } +} diff --git a/src/EFCore.SqlServer.HierarchyId/build/net6.0/EntityFrameworkCore.SqlServer.HierarchyId.targets b/src/EFCore.SqlServer.HierarchyId/build/net6.0/Microsoft.EntityFrameworkCore.SqlServer.HierarchyId.targets similarity index 94% rename from src/EFCore.SqlServer.HierarchyId/build/net6.0/EntityFrameworkCore.SqlServer.HierarchyId.targets rename to src/EFCore.SqlServer.HierarchyId/build/net6.0/Microsoft.EntityFrameworkCore.SqlServer.HierarchyId.targets index 09f8c908850..b445f6e4b57 100644 --- a/src/EFCore.SqlServer.HierarchyId/build/net6.0/EntityFrameworkCore.SqlServer.HierarchyId.targets +++ b/src/EFCore.SqlServer.HierarchyId/build/net6.0/Microsoft.EntityFrameworkCore.SqlServer.HierarchyId.targets @@ -32,7 +32,7 @@ Outputs="$(EFCoreSqlServerHierarchyIdFile)"> - <_Parameter1>Microsoft.EntityFrameworkCore.SqlServer.Design.SqlServerHierarchyIdDesignTimeServices, EntityFrameworkCore.SqlServer.HierarchyId + <_Parameter1>Microsoft.EntityFrameworkCore.SqlServer.Design.Internal.SqlServerHierarchyIdDesignTimeServices, Microsoft.EntityFrameworkCore.SqlServer.HierarchyId <_Parameter2>Microsoft.EntityFrameworkCore.SqlServer diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/CSharpDbContextGeneratorTest.cs b/test/EFCore.SqlServer.HierarchyId.Tests/CSharpDbContextGeneratorTest.cs index dbb660d1f41..31b6636f1b9 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/CSharpDbContextGeneratorTest.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/CSharpDbContextGeneratorTest.cs @@ -1,7 +1,7 @@ -using System; -using Microsoft.EntityFrameworkCore.Internal; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using Microsoft.EntityFrameworkCore.Scaffolding; -using Microsoft.EntityFrameworkCore.SqlServer.Internal; using Xunit; namespace Microsoft.EntityFrameworkCore.SqlServer; @@ -22,7 +22,7 @@ public void Generates_context_with_UseHierarchyId() b.Property("Name"); }); }, - new ModelCodeGenerationOptions {UseDataAnnotations = false}, + new ModelCodeGenerationOptions { UseDataAnnotations = false }, code => { AssertFileContents( diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/CSharpEntityTypeGeneratorTest.cs b/test/EFCore.SqlServer.HierarchyId.Tests/CSharpEntityTypeGeneratorTest.cs index 4dd7005a6ca..77c33b38c74 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/CSharpEntityTypeGeneratorTest.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/CSharpEntityTypeGeneratorTest.cs @@ -1,4 +1,6 @@ -using System.Linq; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using Microsoft.EntityFrameworkCore.Scaffolding; using Xunit; diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/DesignTimeServicesTests.cs b/test/EFCore.SqlServer.HierarchyId.Tests/DesignTimeServicesTests.cs index 65cd1c3d3a2..1b6f94f6d4a 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/DesignTimeServicesTests.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/DesignTimeServicesTests.cs @@ -1,24 +1,26 @@ -using Microsoft.EntityFrameworkCore.Scaffolding; -using Microsoft.EntityFrameworkCore.SqlServer.Design; -using Microsoft.EntityFrameworkCore.SqlServer.Scaffolding; -using Microsoft.EntityFrameworkCore.SqlServer.Storage; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Scaffolding; +using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Scaffolding.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace Microsoft.EntityFrameworkCore.SqlServer +namespace Microsoft.EntityFrameworkCore.SqlServer; + +public class DesignTimeServicesTests { - public class DesignTimeServicesTests + [ConditionalFact] + public void ConfigureDesignTimeServices_works() { - [Fact] - public void ConfigureDesignTimeServices_works() - { - var serviceCollection = new ServiceCollection(); - new SqlServerHierarchyIdDesignTimeServices().ConfigureDesignTimeServices(serviceCollection); - var serviceProvider = serviceCollection.BuildServiceProvider(); + var serviceCollection = new ServiceCollection(); + new SqlServerHierarchyIdDesignTimeServices().ConfigureDesignTimeServices(serviceCollection); + var serviceProvider = serviceCollection.BuildServiceProvider(); - Assert.IsType(serviceProvider.GetService()); - Assert.IsType(serviceProvider.GetService()); - } + Assert.IsType(serviceProvider.GetService()); + Assert.IsType(serviceProvider.GetService()); } } diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/EFCore.SqlServer.HierarchyId.Tests.csproj b/test/EFCore.SqlServer.HierarchyId.Tests/EFCore.SqlServer.HierarchyId.Tests.csproj index 9ac69ed5adb..d976dff0a05 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/EFCore.SqlServer.HierarchyId.Tests.csproj +++ b/test/EFCore.SqlServer.HierarchyId.Tests/EFCore.SqlServer.HierarchyId.Tests.csproj @@ -1,24 +1,18 @@ - + net7.0 - false - EntityFrameworkCore.SqlServer.HierarchyId.Test + Microsoft.EntityFrameworkCore.SqlServer.HierarchyId.Tests Microsoft.EntityFrameworkCore.SqlServer + True + true - - - - - - - + + - - - + diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/MigrationTests.cs b/test/EFCore.SqlServer.HierarchyId.Tests/MigrationTests.cs index deddd3596e0..6864d82fe65 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/MigrationTests.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/MigrationTests.cs @@ -1,4 +1,7 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Migrations.Design; @@ -6,58 +9,57 @@ using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace Microsoft.EntityFrameworkCore.SqlServer +namespace Microsoft.EntityFrameworkCore.SqlServer; + +public class MigrationTests { - public class MigrationTests + private delegate string MigrationCodeGetter(string migrationName, string rootNamespace); + + private delegate string SnapshotCodeGetter(string rootNamespace); + + [ConditionalFact] + public void Migration_and_snapshot_generate_with_typed_array() { - private delegate string MigrationCodeGetter(string migrationName, string rootNamespace); - private delegate string SnapshotCodeGetter(string rootNamespace); - - [Fact] - public void Migration_and_snapshot_generate_with_typed_array() - { - using var db = new TypedArraySeedContext(); - ValidateMigrationAndSnapshotCode(db, db.GetExpectedMigrationCode, db.GetExpectedSnapshotCode); - } - - [Fact] - public void Migration_and_snapshot_generate_with_anonymous_array() - { - using var db = new AnonymousArraySeedContext(); - ValidateMigrationAndSnapshotCode(db, db.GetExpectedMigrationCode, db.GetExpectedSnapshotCode); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "Uses internal efcore apis")] - private static void ValidateMigrationAndSnapshotCode( - DbContext context, - MigrationCodeGetter migrationCodeGetter, - SnapshotCodeGetter snapshotCodeGetter) - { - const string migrationName = "MyMigration"; - const string rootNamespace = "MyApp.Data"; - - var expectedMigration = migrationCodeGetter(migrationName, rootNamespace); - var expectedSnapshot = snapshotCodeGetter(rootNamespace); - - var reporter = new OperationReporter( - new OperationReportHandler( - m => Console.WriteLine($" error: {m}"), - m => Console.WriteLine($" warn: {m}"), - m => Console.WriteLine($" info: {m}"), - m => Console.WriteLine($"verbose: {m}"))); - - var assembly = System.Reflection.Assembly.GetExecutingAssembly(); - - //this works because we have placed the DesignTimeServicesReferenceAttribute - //in the test project's properties, which simulates - //the nuget package's build target - var migration = new DesignTimeServicesBuilder(assembly, assembly, reporter, Array.Empty()) - .Build(context) - .GetRequiredService() - .ScaffoldMigration(migrationName, rootNamespace); - - Assert.Equal(expectedMigration, migration.MigrationCode); - Assert.Equal(expectedSnapshot, migration.SnapshotCode); - } + using var db = new TypedArraySeedContext(); + ValidateMigrationAndSnapshotCode(db, db.GetExpectedMigrationCode, db.GetExpectedSnapshotCode); + } + + [ConditionalFact] + public void Migration_and_snapshot_generate_with_anonymous_array() + { + using var db = new AnonymousArraySeedContext(); + ValidateMigrationAndSnapshotCode(db, db.GetExpectedMigrationCode, db.GetExpectedSnapshotCode); + } + + private static void ValidateMigrationAndSnapshotCode( + DbContext context, + MigrationCodeGetter migrationCodeGetter, + SnapshotCodeGetter snapshotCodeGetter) + { + const string migrationName = "MyMigration"; + const string rootNamespace = "MyApp.Data"; + + var expectedMigration = migrationCodeGetter(migrationName, rootNamespace); + var expectedSnapshot = snapshotCodeGetter(rootNamespace); + + var reporter = new OperationReporter( + new OperationReportHandler( + m => Console.WriteLine($" error: {m}"), + m => Console.WriteLine($" warn: {m}"), + m => Console.WriteLine($" info: {m}"), + m => Console.WriteLine($"verbose: {m}"))); + + var assembly = Assembly.GetExecutingAssembly(); + + //this works because we have placed the DesignTimeServicesReferenceAttribute + //in the test project's properties, which simulates + //the nuget package's build target + var migration = new DesignTimeServicesBuilder(assembly, assembly, reporter, Array.Empty()) + .Build(context) + .GetRequiredService() + .ScaffoldMigration(migrationName, rootNamespace); + + Assert.Equal(expectedMigration, migration.MigrationCode, ignoreLineEndingDifferences: true); + Assert.Equal(expectedSnapshot, migration.SnapshotCode, ignoreLineEndingDifferences: true); } } diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/ModelCodeGeneratorTestBase.cs b/test/EFCore.SqlServer.HierarchyId.Tests/ModelCodeGeneratorTestBase.cs index c1d87785da7..06174e68d0f 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/ModelCodeGeneratorTestBase.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/ModelCodeGeneratorTestBase.cs @@ -1,4 +1,6 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Scaffolding; @@ -8,8 +10,6 @@ namespace Microsoft.EntityFrameworkCore.SqlServer; -#pragma warning disable EF1001 - public abstract class ModelCodeGeneratorTestBase { protected void Test( diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/NullabilityTests.cs b/test/EFCore.SqlServer.HierarchyId.Tests/NullabilityTests.cs index dc717930a68..4a2069871ed 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/NullabilityTests.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/NullabilityTests.cs @@ -1,80 +1,81 @@ -using System.Linq; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using Xunit; -namespace Microsoft.EntityFrameworkCore.SqlServer +namespace Microsoft.EntityFrameworkCore.SqlServer; + +public class NullabilityTests { - public class NullabilityTests + [ConditionalFact] + public void Null_against_null() { - [Fact] - public void Null_against_null() - { - Assert.True((HierarchyId)null == (HierarchyId)null); - Assert.False((HierarchyId)null != (HierarchyId)null); - Assert.False((HierarchyId)null > (HierarchyId)null); - Assert.False((HierarchyId)null >= (HierarchyId)null); - Assert.False((HierarchyId)null < (HierarchyId)null); - Assert.False((HierarchyId)null <= (HierarchyId)null); - } + Assert.True(null == (HierarchyId)null); + Assert.False(null != (HierarchyId)null); + Assert.False(null > (HierarchyId)null); + Assert.False(null >= (HierarchyId)null); + Assert.False(null < (HierarchyId)null); + Assert.False(null <= (HierarchyId)null); + } - [Fact] - public void Null_against_nonNull() - { - var hid = HierarchyId.GetRoot(); - Assert.False(hid == (HierarchyId)null); - Assert.False((HierarchyId)null == hid); + [ConditionalFact] + public void Null_against_nonNull() + { + var hid = HierarchyId.GetRoot(); + Assert.False(hid == null); + Assert.False(null == hid); - Assert.True(hid != (HierarchyId)null); - Assert.True((HierarchyId)null != hid); + Assert.True(hid != null); + Assert.True(null != hid); - Assert.False(hid > (HierarchyId)null); - Assert.False((HierarchyId)null > hid); + Assert.False(hid > null); + Assert.False(null > hid); - Assert.False(hid >= (HierarchyId)null); - Assert.False((HierarchyId)null >= hid); + Assert.False(hid >= null); + Assert.False(null >= hid); - Assert.False(hid < (HierarchyId)null); - Assert.False((HierarchyId)null < hid); + Assert.False(hid < null); + Assert.False(null < hid); - Assert.False(hid <= (HierarchyId)null); - Assert.False((HierarchyId)null <= hid); - } + Assert.False(hid <= null); + Assert.False(null <= hid); + } - [Fact] - public void NullOnly_aggregates_equalTo_null() - { - var hid = (HierarchyId)null; - var collection = new[] { (HierarchyId)null, (HierarchyId)null, }; - var min = collection.Min(); - var max = collection.Max(); + [ConditionalFact] + public void NullOnly_aggregates_equalTo_null() + { + var hid = (HierarchyId)null; + var collection = new[] { null, (HierarchyId)null, }; + var min = collection.Min(); + var max = collection.Max(); - Assert.True(hid == min); - Assert.True(min == hid); - Assert.False(hid != min); - Assert.False(min != hid); + Assert.True(hid == min); + Assert.True(min == hid); + Assert.False(hid != min); + Assert.False(min != hid); - Assert.True(hid == max); - Assert.True(max == hid); - Assert.False(hid != max); - Assert.False(max != hid); - } + Assert.True(hid == max); + Assert.True(max == hid); + Assert.False(hid != max); + Assert.False(max != hid); + } - [Fact] - public void Aggregates_including_nulls_equalTo_nonNull() - { - var hid = HierarchyId.GetRoot(); - var collection = new[] { (HierarchyId)null, (HierarchyId)null, HierarchyId.GetRoot(), HierarchyId.GetRoot(), }; - var min = collection.Min(); - var max = collection.Max(); + [ConditionalFact] + public void Aggregates_including_nulls_equalTo_nonNull() + { + var hid = HierarchyId.GetRoot(); + var collection = new[] { null, null, HierarchyId.GetRoot(), HierarchyId.GetRoot(), }; + var min = collection.Min(); + var max = collection.Max(); - Assert.True(hid == min); - Assert.True(min == hid); - Assert.False(hid != min); - Assert.False(min != hid); + Assert.True(hid == min); + Assert.True(min == hid); + Assert.False(hid != min); + Assert.False(min != hid); - Assert.True(hid == max); - Assert.True(max == hid); - Assert.False(hid != max); - Assert.False(max != hid); - } + Assert.True(hid == max); + Assert.True(max == hid); + Assert.False(hid != max); + Assert.False(max != hid); } } diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/Properties/EFCoreSqlServerHierarchyId.cs b/test/EFCore.SqlServer.HierarchyId.Tests/Properties/EFCoreSqlServerHierarchyId.cs deleted file mode 100644 index 4b04f36c8db..00000000000 --- a/test/EFCore.SqlServer.HierarchyId.Tests/Properties/EFCoreSqlServerHierarchyId.cs +++ /dev/null @@ -1,18 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: Microsoft.EntityFrameworkCore.Design.DesignTimeServicesReferenceAttribute("Microsoft.EntityFrameworkCore.SqlServer.Design.SqlServerHierarchyIdDesignTimeServ" + - "ices, EntityFrameworkCore.SqlServer.HierarchyId", "Microsoft.EntityFrameworkCore.SqlServer")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/QueryTests.cs b/test/EFCore.SqlServer.HierarchyId.Tests/QueryTests.cs index e07e3fa6676..943f0a21044 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/QueryTests.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/QueryTests.cs @@ -1,343 +1,340 @@ -using System; -using System.Collections.Generic; -using System.Linq; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using Microsoft.EntityFrameworkCore.SqlServer.Test.Models; +using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; -namespace Microsoft.EntityFrameworkCore.SqlServer +namespace Microsoft.EntityFrameworkCore.SqlServer; + +[SqlServerConfiguredCondition] +public class QueryTests : IDisposable { - public class QueryTests : IDisposable - { - private readonly AbrahamicContext _db; + private readonly AbrahamicContext _db; - public QueryTests() - { - _db = new AbrahamicContext(); - _db.Database.EnsureDeleted(); - _db.Database.EnsureCreated(); - } + public QueryTests() + { + _db = new AbrahamicContext(); + _db.Database.EnsureDeleted(); + _db.Database.EnsureCreated(); + _db.ClearSql(); + } - [Fact] - public void GetLevel_can_translate() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where p.Id.GetLevel() == 0 - select p.Name); + [ConditionalFact] + public void GetLevel_can_translate() + { + var results = (from p in _db.Patriarchy + where p.Id.GetLevel() == 0 + select p.Name).ToList(); - Assert.Equal( - condense(@"SELECT [p].[Name] FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(0 AS smallint)"), - condense(_db.Sql)); + Assert.Equal( + Condense(@"SELECT [p].[Name] FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(0 AS smallint)"), + Condense(_db.Sql)); - Assert.Equal(new[] { "Abraham" }, results); - } + Assert.Equal(new[] { "Abraham" }, results); + } - [Fact] - public void IsDescendantOf_can_translate() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where p.Id.GetLevel() == 3 - select p.Id.IsDescendantOf(p.Id.GetAncestor(1))); + [ConditionalFact] + public void IsDescendantOf_can_translate() + { + var results = (from p in _db.Patriarchy + where p.Id.GetLevel() == 3 + select p.Id.IsDescendantOf(p.Id.GetAncestor(1))).ToList(); - Assert.Equal( - condense(@"SELECT [p].[Id].IsDescendantOf([p].[Id].GetAncestor(1)) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(3 AS smallint)"), - condense(_db.Sql)); + Assert.Equal( + Condense( + @"SELECT [p].[Id].IsDescendantOf([p].[Id].GetAncestor(1)) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(3 AS smallint)"), + Condense(_db.Sql)); - Assert.All(results, b => Assert.True(b)); - } + Assert.All(results, b => Assert.True(b)); + } - [Fact] - public void GetAncestor_0_can_translate() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where p.Id.GetLevel() == 0 - select p.Id.GetAncestor(0)); + [ConditionalFact] + public void GetAncestor_0_can_translate() + { + var results = (from p in _db.Patriarchy + where p.Id.GetLevel() == 0 + select p.Id.GetAncestor(0)).ToList(); - Assert.Equal( - condense(@"SELECT [p].[Id].GetAncestor(0) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(0 AS smallint)"), - condense(_db.Sql)); + Assert.Equal( + Condense(@"SELECT [p].[Id].GetAncestor(0) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(0 AS smallint)"), + Condense(_db.Sql)); - Assert.All(results, h => Assert.Equal(HierarchyId.GetRoot(), h)); - } + Assert.All(results, h => Assert.Equal(HierarchyId.GetRoot(), h)); + } - [Fact] - public void GetAncestor_1_can_translate() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where p.Id.GetLevel() == 1 - select p.Id.GetAncestor(1)); + [ConditionalFact] + public void GetAncestor_1_can_translate() + { + var results = (from p in _db.Patriarchy + where p.Id.GetLevel() == 1 + select p.Id.GetAncestor(1)).ToList(); - Assert.Equal( - condense(@"SELECT [p].[Id].GetAncestor(1) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(1 AS smallint)"), - condense(_db.Sql)); + Assert.Equal( + Condense(@"SELECT [p].[Id].GetAncestor(1) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(1 AS smallint)"), + Condense(_db.Sql)); - Assert.All(results, h => Assert.Equal(HierarchyId.GetRoot(), h)); - } + Assert.All(results, h => Assert.Equal(HierarchyId.GetRoot(), h)); + } - [Fact] - public void GetAncestor_2_can_translate() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where p.Id.GetLevel() == 2 - select p.Id.GetAncestor(2)); + [ConditionalFact] + public void GetAncestor_2_can_translate() + { + var results = (from p in _db.Patriarchy + where p.Id.GetLevel() == 2 + select p.Id.GetAncestor(2)).ToList(); - Assert.Equal( - condense(@"SELECT [p].[Id].GetAncestor(2) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(2 AS smallint)"), - condense(_db.Sql)); + Assert.Equal( + Condense(@"SELECT [p].[Id].GetAncestor(2) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(2 AS smallint)"), + Condense(_db.Sql)); - Assert.All(results, h => Assert.Equal(HierarchyId.GetRoot(), h)); - } + Assert.All(results, h => Assert.Equal(HierarchyId.GetRoot(), h)); + } - [Fact] - public void GetAncestor_3_can_translate() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where p.Id.GetLevel() == 3 - select p.Id.GetAncestor(3)); + [ConditionalFact] + public void GetAncestor_3_can_translate() + { + var results = (from p in _db.Patriarchy + where p.Id.GetLevel() == 3 + select p.Id.GetAncestor(3)).ToList(); - Assert.Equal( - condense(@"SELECT [p].[Id].GetAncestor(3) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(3 AS smallint)"), - condense(_db.Sql)); + Assert.Equal( + Condense(@"SELECT [p].[Id].GetAncestor(3) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(3 AS smallint)"), + Condense(_db.Sql)); - Assert.All(results, h => Assert.Equal(HierarchyId.GetRoot(), h)); - } + Assert.All(results, h => Assert.Equal(HierarchyId.GetRoot(), h)); + } - [Fact] - public void GetAncestor_of_root_returns_null() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where p.Id.GetLevel() == 0 - select p.Id.GetAncestor(1)); + [ConditionalFact] + public void GetAncestor_of_root_returns_null() + { + var results = (from p in _db.Patriarchy + where p.Id.GetLevel() == 0 + select p.Id.GetAncestor(1)).ToList(); - Assert.Equal( - condense(@"SELECT [p].[Id].GetAncestor(1) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(0 AS smallint)"), - condense(_db.Sql)); + Assert.Equal( + Condense(@"SELECT [p].[Id].GetAncestor(1) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(0 AS smallint)"), + Condense(_db.Sql)); - Assert.Equal(new HierarchyId[] { null }, results); - } + Assert.Equal(new HierarchyId[] { null }, results); + } - [Fact] - public void GetDescendent_can_translate() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where p.Id.GetLevel() == 0 - select p.Id.GetDescendant(null, null)); + [ConditionalFact] + public void GetDescendent_can_translate() + { + var results = (from p in _db.Patriarchy + where p.Id.GetLevel() == 0 + select p.Id.GetDescendant(null, null)).ToList(); - Assert.Equal( - condense(@"SELECT [p].[Id].GetDescendant(NULL, NULL) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(0 AS smallint)"), - condense(_db.Sql)); + Assert.Equal( + Condense(@"SELECT [p].[Id].GetDescendant(NULL, NULL) FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(0 AS smallint)"), + Condense(_db.Sql)); - Assert.Equal(new[] { HierarchyId.Parse("/1/") }, results); - } + Assert.Equal(new[] { HierarchyId.Parse("/1/") }, results); + } - [Fact] - public void HierarchyId_can_be_sent_as_parameter() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where p.Id == HierarchyId.Parse("/1/") - select p.Name); + [ConditionalFact] + public void HierarchyId_can_be_sent_as_parameter() + { + var results = (from p in _db.Patriarchy + where p.Id == HierarchyId.Parse("/1/") + select p.Name).ToList(); - Assert.Equal( - condense(@"SELECT [p].[Name] FROM [Patriarchy] AS [p] WHERE [p].[Id] = '/1/'"), - condense(_db.Sql)); + Assert.Equal( + Condense(@"SELECT [p].[Name] FROM [Patriarchy] AS [p] WHERE [p].[Id] = '/1/'"), + Condense(_db.Sql)); - Assert.Equal(new[] { "Isaac" }, results); - } + Assert.Equal(new[] { "Isaac" }, results); + } - [Fact] - public void Converted_HierarchyId_can_be_sent_as_parameter() - { - var results = Enumerable.ToList( - from p in _db.ConvertedPatriarchy - where p.HierarchyId == HierarchyId.Parse("/1/").ToString() - select p.Name); + [ConditionalFact] + public void Converted_HierarchyId_can_be_sent_as_parameter() + { + var results = (from p in _db.ConvertedPatriarchy + where p.HierarchyId == HierarchyId.Parse("/1/").ToString() + select p.Name).ToList(); - Assert.Equal( - condense(@"SELECT [c].[Name] FROM [ConvertedPatriarchy] AS [c] WHERE [c].[HierarchyId] = '/1/'"), - condense(_db.Sql)); + Assert.Equal( + Condense(@"SELECT [c].[Name] FROM [ConvertedPatriarchy] AS [c] WHERE [c].[HierarchyId] = '/1/'"), + Condense(_db.Sql)); - Assert.Equal(new[] { "Isaac" }, results); - } + Assert.Equal(new[] { "Isaac" }, results); + } - [Fact] - public void Can_insert_HierarchyId() + [ConditionalFact] + public void Can_insert_HierarchyId() + { + using (_db.Database.BeginTransaction()) { - using (_db.Database.BeginTransaction()) + var entities = new List { - var entities = new List - { - new() { Id = HierarchyId.Parse("/2/1/"), Name = "Thrór" }, - new() { Id = HierarchyId.Parse("/2/2/"), Name = "Thráin II" }, - new() { Id = HierarchyId.Parse("/3/"), Name = "Thorin Oakenshield" } - }; + new() { Id = HierarchyId.Parse("/2/1/"), Name = "Thrór" }, + new() { Id = HierarchyId.Parse("/2/2/"), Name = "Thráin II" }, + new() { Id = HierarchyId.Parse("/3/"), Name = "Thorin Oakenshield" } + }; - _db.AddRange(entities); - _db.SaveChanges(); - _db.ChangeTracker.Clear(); + _db.AddRange(entities); + _db.SaveChanges(); + _db.ChangeTracker.Clear(); - var queried = _db.Patriarchy.Where(e => e.Name.StartsWith("Th")).OrderBy(e => e.Id).ToList(); + var queried = _db.Patriarchy.Where(e => e.Name.StartsWith("Th")).OrderBy(e => e.Id).ToList(); - Assert.Equal(3, queried.Count); + Assert.Equal(3, queried.Count); - Assert.Equal(HierarchyId.Parse("/2/1/"), queried[0].Id); - Assert.Equal("Thrór", queried[0].Name); + Assert.Equal(HierarchyId.Parse("/2/1/"), queried[0].Id); + Assert.Equal("Thrór", queried[0].Name); - Assert.Equal(HierarchyId.Parse("/2/2/"), queried[1].Id); - Assert.Equal("Thráin II", queried[1].Name); + Assert.Equal(HierarchyId.Parse("/2/2/"), queried[1].Id); + Assert.Equal("Thráin II", queried[1].Name); - Assert.Equal(HierarchyId.Parse("/3/"), queried[2].Id); - Assert.Equal("Thorin Oakenshield", queried[2].Name); - } + Assert.Equal(HierarchyId.Parse("/3/"), queried[2].Id); + Assert.Equal("Thorin Oakenshield", queried[2].Name); } + } - [Fact] - public void Can_insert_and_update_converted_HierarchyId() + [ConditionalFact] + public void Can_insert_and_update_converted_HierarchyId() + { + using (_db.Database.BeginTransaction()) { - using (_db.Database.BeginTransaction()) + var entities = new List { - var entities = new List - { - new() { HierarchyId = HierarchyId.Parse("/2/1/").ToString(), Name = "Thrór" }, - new() { HierarchyId = HierarchyId.Parse("/2/2/").ToString(), Name = "Thráin II" }, - new() { HierarchyId = HierarchyId.Parse("/3/").ToString(), Name = "Thorin Oakenshield" } - }; + new() { HierarchyId = HierarchyId.Parse("/2/1/").ToString(), Name = "Thrór" }, + new() { HierarchyId = HierarchyId.Parse("/2/2/").ToString(), Name = "Thráin II" }, + new() { HierarchyId = HierarchyId.Parse("/3/").ToString(), Name = "Thorin Oakenshield" } + }; - _db.AddRange(entities); - _db.SaveChanges(); - _db.ChangeTracker.Clear(); + _db.AddRange(entities); + _db.SaveChanges(); + _db.ChangeTracker.Clear(); - var queried = _db.ConvertedPatriarchy.Where(e => e.Name.StartsWith("Th")).OrderBy(e => e.Id).ToList(); + var queried = _db.ConvertedPatriarchy.Where(e => e.Name.StartsWith("Th")).OrderBy(e => e.Id).ToList(); - Assert.Equal(3, queried.Count); + Assert.Equal(3, queried.Count); - Assert.Equal(HierarchyId.Parse("/2/1/").ToString(), queried[0].HierarchyId); - Assert.Equal("Thrór", queried[0].Name); + Assert.Equal(HierarchyId.Parse("/2/1/").ToString(), queried[0].HierarchyId); + Assert.Equal("Thrór", queried[0].Name); - Assert.Equal(HierarchyId.Parse("/2/2/").ToString(), queried[1].HierarchyId); - Assert.Equal("Thráin II", queried[1].Name); + Assert.Equal(HierarchyId.Parse("/2/2/").ToString(), queried[1].HierarchyId); + Assert.Equal("Thráin II", queried[1].Name); - Assert.Equal(HierarchyId.Parse("/3/").ToString(), queried[2].HierarchyId); - Assert.Equal("Thorin Oakenshield", queried[2].Name); + Assert.Equal(HierarchyId.Parse("/3/").ToString(), queried[2].HierarchyId); + Assert.Equal("Thorin Oakenshield", queried[2].Name); - queried[2].HierarchyId = "/3/1/"; + queried[2].HierarchyId = "/3/1/"; - _db.SaveChanges(); - _db.ChangeTracker.Clear(); + _db.SaveChanges(); + _db.ChangeTracker.Clear(); - queried = _db.ConvertedPatriarchy.Where(e => e.Name.StartsWith("Th")).OrderBy(e => e.Id).ToList(); + queried = _db.ConvertedPatriarchy.Where(e => e.Name.StartsWith("Th")).OrderBy(e => e.Id).ToList(); - Assert.Equal(3, queried.Count); + Assert.Equal(3, queried.Count); - Assert.Equal(HierarchyId.Parse("/2/1/").ToString(), queried[0].HierarchyId); - Assert.Equal("Thrór", queried[0].Name); + Assert.Equal(HierarchyId.Parse("/2/1/").ToString(), queried[0].HierarchyId); + Assert.Equal("Thrór", queried[0].Name); - Assert.Equal(HierarchyId.Parse("/2/2/").ToString(), queried[1].HierarchyId); - Assert.Equal("Thráin II", queried[1].Name); + Assert.Equal(HierarchyId.Parse("/2/2/").ToString(), queried[1].HierarchyId); + Assert.Equal("Thráin II", queried[1].Name); - Assert.Equal(HierarchyId.Parse("/3/1/").ToString(), queried[2].HierarchyId); - Assert.Equal("Thorin Oakenshield", queried[2].Name); - } + Assert.Equal(HierarchyId.Parse("/3/1/").ToString(), queried[2].HierarchyId); + Assert.Equal("Thorin Oakenshield", queried[2].Name); } + } - [Fact] - public void HierarchyId_get_ancestor_of_level_is_root() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where p.Id.GetAncestor(p.Id.GetLevel()) == HierarchyId.GetRoot() // HierarchyId.Parse("/1/") // HierarchyId.Parse(p.Id.ToString()).GetAncestor(HierarchyId.Parse(p.Id.ToString()).GetLevel()) - select p.Name); + [ConditionalFact] + public void HierarchyId_get_ancestor_of_level_is_root() + { + var results = (from p in _db.Patriarchy + where + p.Id.GetAncestor(p.Id.GetLevel()) + == HierarchyId + .GetRoot() // HierarchyId.Parse("/1/") // HierarchyId.Parse(p.Id.ToString()).GetAncestor(HierarchyId.Parse(p.Id.ToString()).GetLevel()) + select p.Name).ToList(); - Assert.Equal( - condense(@"SELECT [p].[Name] FROM [Patriarchy] AS [p] WHERE [p].[Id].GetAncestor(CAST([p].[Id].GetLevel() AS int)) = '/'"), - condense(_db.Sql)); + Assert.Equal( + Condense(@"SELECT [p].[Name] FROM [Patriarchy] AS [p] WHERE [p].[Id].GetAncestor(CAST([p].[Id].GetLevel() AS int)) = '/'"), + Condense(_db.Sql)); - var all = Enumerable.ToList( - from p in _db.Patriarchy - select p.Name); + var all = (from p in _db.Patriarchy + select p.Name).ToList(); - Assert.Equal(all, results); - } + Assert.Equal(all, results); + } - [Fact] - public void HierarchyId_can_call_method_on_parameter() - { - var isaac = HierarchyId.Parse("/1/"); + [ConditionalFact] + public void HierarchyId_can_call_method_on_parameter() + { + var isaac = HierarchyId.Parse("/1/"); - var results = Enumerable.ToList( - from p in _db.Patriarchy - where isaac.IsDescendantOf(p.Id) - select p.Name); + var results = (from p in _db.Patriarchy + where isaac.IsDescendantOf(p.Id) + select p.Name).ToList(); - Assert.Equal( - condense(@"SELECT [p].[Name] FROM [Patriarchy] AS [p] WHERE @__isaac_0.IsDescendantOf([p].[Id]) = CAST(1 AS bit)"), - condense(_db.Sql)); + Assert.Equal( + """ + @__isaac_0='?' (Size = 1) (DbType = Object) - Assert.Equal(new[] { "Abraham", "Isaac" }, results); - } + SELECT [p].[Name] + FROM [Patriarchy] AS [p] + WHERE @__isaac_0.IsDescendantOf([p].[Id]) = CAST(1 AS bit) + """, + _db.Sql, + ignoreLineEndingDifferences: true); - [Fact] - public void ToString_can_translate() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where p.Id.GetLevel() == 1 - select p.Id.ToString()); + Assert.Equal(new[] { "Abraham", "Isaac" }, results); + } - Assert.Equal( - condense(@"SELECT [p].[Id].ToString() FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(1 AS smallint)"), - condense(_db.Sql)); + [ConditionalFact] + public void ToString_can_translate() + { + var results = (from p in _db.Patriarchy + where p.Id.GetLevel() == 1 + select p.Id.ToString()).ToList(); - Assert.Equal(new[] { "/1/" }, results); - } + Assert.Equal( + Condense(@"SELECT [p].[Id].ToString() FROM [Patriarchy] AS [p] WHERE [p].[Id].GetLevel() = CAST(1 AS smallint)"), + Condense(_db.Sql)); - [Fact] - public void ToString_can_translate_redux() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where EF.Functions.Like(p.Id.ToString(), "%/1/") - select p.Name); + Assert.Equal(new[] { "/1/" }, results); + } - Assert.Equal( - condense(@"SELECT [p].[Name] FROM [Patriarchy] AS [p] WHERE [p].[Id].ToString() LIKE N'%/1/'"), - condense(_db.Sql)); + [ConditionalFact] + public void ToString_can_translate_redux() + { + var results = (from p in _db.Patriarchy + where EF.Functions.Like(p.Id.ToString(), "%/1/") + select p.Name).ToList(); - Assert.Equal(new[] { "Isaac", "Jacob", "Reuben" }, results); - } + Assert.Equal( + Condense(@"SELECT [p].[Name] FROM [Patriarchy] AS [p] WHERE [p].[Id].ToString() LIKE N'%/1/'"), + Condense(_db.Sql)); - [Fact] - public void Parse_can_translate() - { - var results = Enumerable.ToList( - from p in _db.Patriarchy - where p.Id == HierarchyId.GetRoot() - select HierarchyId.Parse(p.Id.ToString())); + Assert.Equal(new[] { "Isaac", "Jacob", "Reuben" }, results); + } - Assert.Equal( - condense(@"SELECT hierarchyid::Parse([p].[Id].ToString()) FROM [Patriarchy] AS [p] WHERE [p].[Id] = '/'"), - condense(_db.Sql)); + [ConditionalFact] + public void Parse_can_translate() + { + var results = (from p in _db.Patriarchy + where p.Id == HierarchyId.GetRoot() + select HierarchyId.Parse(p.Id.ToString())).ToList(); - Assert.Equal(new[] { HierarchyId.Parse("/") }, results); - } + Assert.Equal( + Condense(@"SELECT hierarchyid::Parse([p].[Id].ToString()) FROM [Patriarchy] AS [p] WHERE [p].[Id] = '/'"), + Condense(_db.Sql)); - public void Dispose() - { - _db.Dispose(); - } + Assert.Equal(new[] { HierarchyId.Parse("/") }, results); + } - // replace whitespace with a single space - private static string condense(string str) - { - var split = str.Split((char[])null, StringSplitOptions.RemoveEmptyEntries); - return string.Join(" ", split); - } + public void Dispose() + { + _db.Dispose(); + } + + // replace whitespace with a single space + private static string Condense(string str) + { + var split = str.Split((char[])null, StringSplitOptions.RemoveEmptyEntries); + return string.Join(" ", split); } } diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/RelationalScaffoldingModelFactoryTest.cs b/test/EFCore.SqlServer.HierarchyId.Tests/RelationalScaffoldingModelFactoryTest.cs index 987d6865a1e..543499cbf67 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/RelationalScaffoldingModelFactoryTest.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/RelationalScaffoldingModelFactoryTest.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Scaffolding; @@ -8,8 +11,6 @@ namespace Microsoft.EntityFrameworkCore.SqlServer; -#pragma warning disable EF1001 - public class RelationalScaffoldingModelFactoryTest { private readonly IScaffoldingModelFactory _factory; diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Logging/TestLogger.cs b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Logging/TestLogger.cs deleted file mode 100644 index 3906af28440..00000000000 --- a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Logging/TestLogger.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.Extensions.Logging; - -namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Logging -{ - public class TestLogger : ILogger - { - public string Sql { get; set; } - - public IDisposable BeginScope(TState state) - => null; - - public bool IsEnabled(LogLevel logLevel) - => true; - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - if (eventId != RelationalEventId.CommandExecuting) - return; - - var structure = (IReadOnlyList>)state; - var commandText = (string)structure.FirstOrDefault(i => i.Key == "commandText").Value; - - Sql = commandText; - } - } -} diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Logging/TestLoggerFactory.cs b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Logging/TestLoggerFactory.cs deleted file mode 100644 index 0d2f8efd9bb..00000000000 --- a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Logging/TestLoggerFactory.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; - -namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Logging -{ - class TestLoggerFactory : ILoggerFactory - { - public TestLogger Logger { get; } - = new TestLogger(); - - public void AddProvider(ILoggerProvider provider) - => throw new NotImplementedException(); - - public ILogger CreateLogger(string categoryName) - => Logger; - - public void Dispose() - { - } - } -} diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/AbrahamicContext.cs b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/AbrahamicContext.cs index 8b3f94d9aa6..5a63392b9c5 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/AbrahamicContext.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/AbrahamicContext.cs @@ -1,69 +1,154 @@ -using Microsoft.EntityFrameworkCore.SqlServer.Test.Logging; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Models +using Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Models; + +internal class AbrahamicContext : DbContext { - class AbrahamicContext : DbContext - { - readonly TestLoggerFactory _loggerFactory - = new TestLoggerFactory(); + private readonly TestSqlLoggerFactory _loggerFactory = new(); - public DbSet Patriarchy { get; set; } - public DbSet ConvertedPatriarchy { get; set; } + public DbSet Patriarchy { get; set; } + public DbSet ConvertedPatriarchy { get; set; } - public string Sql - => _loggerFactory.Logger.Sql; + public string Sql + => _loggerFactory.Sql; - protected override void OnConfiguring(DbContextOptionsBuilder options) - => options - .UseSqlServer( - @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=HierarchyIdTests", - x => x.UseHierarchyId()) - .UseLoggerFactory(_loggerFactory); + public void ClearSql() + => _loggerFactory.Clear(); - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity() - .HasData( - new Patriarch { Id = HierarchyId.GetRoot(), Name = "Abraham" }, - new Patriarch { Id = HierarchyId.Parse("/1/"), Name = "Isaac" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/"), Name = "Jacob" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/1/"), Name = "Reuben" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/2/"), Name = "Simeon" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/3/"), Name = "Levi" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/4/"), Name = "Judah" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/5/"), Name = "Issachar" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/6/"), Name = "Zebulun" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/7/"), Name = "Dan" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/8/"), Name = "Naphtali" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/9/"), Name = "Gad" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/10/"), Name = "Asher" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/11.1/"), Name = "Ephraim" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/11.2/"), Name = "Manasseh" }, - new Patriarch { Id = HierarchyId.Parse("/1/1/12/"), Name = "Benjamin" }); + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options + .UseSqlServer( + SqlServerTestStore.CreateConnectionString("HierarchyIdTests"), + x => x.UseHierarchyId()) + .UseLoggerFactory(_loggerFactory); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasData( + new Patriarch { Id = HierarchyId.GetRoot(), Name = "Abraham" }, + new Patriarch { Id = HierarchyId.Parse("/1/"), Name = "Isaac" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/"), Name = "Jacob" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/1/"), Name = "Reuben" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/2/"), Name = "Simeon" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/3/"), Name = "Levi" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/4/"), Name = "Judah" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/5/"), Name = "Issachar" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/6/"), Name = "Zebulun" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/7/"), Name = "Dan" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/8/"), Name = "Naphtali" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/9/"), Name = "Gad" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/10/"), Name = "Asher" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/11.1/"), Name = "Ephraim" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/11.2/"), Name = "Manasseh" }, + new Patriarch { Id = HierarchyId.Parse("/1/1/12/"), Name = "Benjamin" }); - modelBuilder.Entity(b => + modelBuilder.Entity( + b => { b.Property(e => e.HierarchyId) .HasConversion(v => HierarchyId.Parse(v), v => v.ToString()); b.HasData( - new ConvertedPatriarch { Id = 1, HierarchyId = HierarchyId.GetRoot().ToString(), Name = "Abraham" }, - new ConvertedPatriarch { Id = 2, HierarchyId = HierarchyId.Parse("/1/").ToString(), Name = "Isaac" }, - new ConvertedPatriarch { Id = 3, HierarchyId = HierarchyId.Parse("/1/1/").ToString(), Name = "Jacob" }, - new ConvertedPatriarch { Id = 4, HierarchyId = HierarchyId.Parse("/1/1/1/").ToString(), Name = "Reuben" }, - new ConvertedPatriarch { Id = 5, HierarchyId = HierarchyId.Parse("/1/1/2/").ToString(), Name = "Simeon" }, - new ConvertedPatriarch { Id = 6, HierarchyId = HierarchyId.Parse("/1/1/3/").ToString(), Name = "Levi" }, - new ConvertedPatriarch { Id = 7, HierarchyId = HierarchyId.Parse("/1/1/4/").ToString(), Name = "Judah" }, - new ConvertedPatriarch { Id = 8, HierarchyId = HierarchyId.Parse("/1/1/5/").ToString(), Name = "Issachar" }, - new ConvertedPatriarch { Id = 9, HierarchyId = HierarchyId.Parse("/1/1/6/").ToString(), Name = "Zebulun" }, - new ConvertedPatriarch { Id = 10, HierarchyId = HierarchyId.Parse("/1/1/7/").ToString(), Name = "Dan" }, - new ConvertedPatriarch { Id = 11, HierarchyId = HierarchyId.Parse("/1/1/8/").ToString(), Name = "Naphtali" }, - new ConvertedPatriarch { Id = 12, HierarchyId = HierarchyId.Parse("/1/1/9/").ToString(), Name = "Gad" }, - new ConvertedPatriarch { Id = 13, HierarchyId = HierarchyId.Parse("/1/1/10/").ToString(), Name = "Asher" }, - new ConvertedPatriarch { Id = 14, HierarchyId = HierarchyId.Parse("/1/1/11.1/").ToString(), Name = "Ephraim" }, - new ConvertedPatriarch { Id = 15, HierarchyId = HierarchyId.Parse("/1/1/11.2/").ToString(), Name = "Manasseh" }, - new ConvertedPatriarch { Id = 16, HierarchyId = HierarchyId.Parse("/1/1/12/").ToString(), Name = "Benjamin" }); + new ConvertedPatriarch + { + Id = 1, + HierarchyId = HierarchyId.GetRoot().ToString(), + Name = "Abraham" + }, + new ConvertedPatriarch + { + Id = 2, + HierarchyId = HierarchyId.Parse("/1/").ToString(), + Name = "Isaac" + }, + new ConvertedPatriarch + { + Id = 3, + HierarchyId = HierarchyId.Parse("/1/1/").ToString(), + Name = "Jacob" + }, + new ConvertedPatriarch + { + Id = 4, + HierarchyId = HierarchyId.Parse("/1/1/1/").ToString(), + Name = "Reuben" + }, + new ConvertedPatriarch + { + Id = 5, + HierarchyId = HierarchyId.Parse("/1/1/2/").ToString(), + Name = "Simeon" + }, + new ConvertedPatriarch + { + Id = 6, + HierarchyId = HierarchyId.Parse("/1/1/3/").ToString(), + Name = "Levi" + }, + new ConvertedPatriarch + { + Id = 7, + HierarchyId = HierarchyId.Parse("/1/1/4/").ToString(), + Name = "Judah" + }, + new ConvertedPatriarch + { + Id = 8, + HierarchyId = HierarchyId.Parse("/1/1/5/").ToString(), + Name = "Issachar" + }, + new ConvertedPatriarch + { + Id = 9, + HierarchyId = HierarchyId.Parse("/1/1/6/").ToString(), + Name = "Zebulun" + }, + new ConvertedPatriarch + { + Id = 10, + HierarchyId = HierarchyId.Parse("/1/1/7/").ToString(), + Name = "Dan" + }, + new ConvertedPatriarch + { + Id = 11, + HierarchyId = HierarchyId.Parse("/1/1/8/").ToString(), + Name = "Naphtali" + }, + new ConvertedPatriarch + { + Id = 12, + HierarchyId = HierarchyId.Parse("/1/1/9/").ToString(), + Name = "Gad" + }, + new ConvertedPatriarch + { + Id = 13, + HierarchyId = HierarchyId.Parse("/1/1/10/").ToString(), + Name = "Asher" + }, + new ConvertedPatriarch + { + Id = 14, + HierarchyId = HierarchyId.Parse("/1/1/11.1/").ToString(), + Name = "Ephraim" + }, + new ConvertedPatriarch + { + Id = 15, + HierarchyId = HierarchyId.Parse("/1/1/11.2/").ToString(), + Name = "Manasseh" + }, + new ConvertedPatriarch + { + Id = 16, + HierarchyId = HierarchyId.Parse("/1/1/12/").ToString(), + Name = "Benjamin" + }); }); - } } } diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/ConvertedPatriarch.cs b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/ConvertedPatriarch.cs index a5ca81ce2b1..a7c449612df 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/ConvertedPatriarch.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/ConvertedPatriarch.cs @@ -1,9 +1,11 @@ -namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Models +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Models; + +internal class ConvertedPatriarch { - class ConvertedPatriarch - { - public int Id { get; set; } - public string HierarchyId { get; set; } - public string Name { get; set; } - } -} \ No newline at end of file + public int Id { get; set; } + public string HierarchyId { get; set; } + public string Name { get; set; } +} diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Migrations/AnonymousArraySeedContext.cs b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Migrations/AnonymousArraySeedContext.cs index b448b23ca89..2ea2780dfff 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Migrations/AnonymousArraySeedContext.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Migrations/AnonymousArraySeedContext.cs @@ -1,33 +1,52 @@ -using Microsoft.EntityFrameworkCore.SqlServer.Storage; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Models.Migrations +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Models.Migrations; + +internal sealed class AnonymousArraySeedContext : MigrationContext { - internal sealed class AnonymousArraySeedContext : MigrationContext + protected override void OnModelCreating(ModelBuilder modelBuilder) { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - RemoveVariableModelAnnotations(modelBuilder); + RemoveVariableModelAnnotations(modelBuilder); - modelBuilder.Entity().HasData( - new { Id = HierarchyId.GetRoot(), Name = "Eddard Stark" }, - new { Id = HierarchyId.Parse("/1/"), Name = "Robb Stark" }, - new { Id = HierarchyId.Parse("/2/"), Name = "Jon Snow" }); + modelBuilder.Entity().HasData( + new { Id = HierarchyId.GetRoot(), Name = "Eddard Stark" }, + new { Id = HierarchyId.Parse("/1/"), Name = "Robb Stark" }, + new { Id = HierarchyId.Parse("/2/"), Name = "Jon Snow" }); - modelBuilder.Entity(b => + modelBuilder.Entity( + b => { b.Property(e => e.HierarchyId) .HasConversion(v => HierarchyId.Parse(v), v => v.ToString()); b.HasData( - new ConvertedPatriarch { Id = 1, HierarchyId = HierarchyId.GetRoot().ToString(), Name = "Eddard Stark" }, - new ConvertedPatriarch { Id = 2, HierarchyId = HierarchyId.Parse("/1/").ToString(), Name = "Robb Stark" }, - new ConvertedPatriarch { Id = 3, HierarchyId = HierarchyId.Parse("/2/").ToString(), Name = "Jon Snow" }); + new ConvertedPatriarch + { + Id = 1, + HierarchyId = HierarchyId.GetRoot().ToString(), + Name = "Eddard Stark" + }, + new ConvertedPatriarch + { + Id = 2, + HierarchyId = HierarchyId.Parse("/1/").ToString(), + Name = "Robb Stark" + }, + new ConvertedPatriarch + { + Id = 3, + HierarchyId = HierarchyId.Parse("/2/").ToString(), + Name = "Jon Snow" + }); }); - } + } - public override string GetExpectedMigrationCode(string migrationName, string rootNamespace) - { - return $@"using Microsoft.EntityFrameworkCore; + public override string GetExpectedMigrationCode(string migrationName, string rootNamespace) + { + return $@"using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable @@ -100,11 +119,11 @@ protected override void Down(MigrationBuilder migrationBuilder) }} }} "; - } + } - public override string GetExpectedSnapshotCode(string rootNamespace) - { - return $@"// + public override string GetExpectedSnapshotCode(string rootNamespace) + { + return $@"// using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using {ThisType.Namespace}; @@ -192,6 +211,5 @@ protected override void BuildModel(ModelBuilder modelBuilder) }} }} "; - } } } diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Migrations/MigrationContext.cs b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Migrations/MigrationContext.cs index 940d136502f..442b86efedb 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Migrations/MigrationContext.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Migrations/MigrationContext.cs @@ -1,48 +1,49 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; -namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Models.Migrations +namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Models.Migrations; + +internal abstract class MigrationContext : DbContext + where TEntity1 : class + where TEntity2 : class { - internal abstract class MigrationContext : DbContext - where TEntity1 : class - where TEntity2 : class + protected Type ModelType1 { get; } = typeof(TEntity1); + protected Type ModelType2 { get; } = typeof(TEntity2); + + private Type _thisType; + + protected Type ThisType + => _thisType ??= GetType(); + + public DbSet TestModels { get; set; } + public DbSet ConvertedTestModels { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options + .UseSqlServer( + @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=HierarchyIdMigrationTests", + x => x.UseHierarchyId()); + + /// + /// Removes annotations from the model that can + /// change between versions of ef. + /// This should be called during OnModelCreating + /// + protected void RemoveVariableModelAnnotations(ModelBuilder modelBuilder) { - protected Type ModelType1 { get; } = typeof(TEntity1); - protected Type ModelType2 { get; } = typeof(TEntity2); - - private Type _thisType; - protected Type ThisType => _thisType ??= GetType(); - - public DbSet TestModels { get; set; } - public DbSet ConvertedTestModels { get; set; } - - protected override void OnConfiguring(DbContextOptionsBuilder options) - => options - .UseSqlServer( - @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=HierarchyIdMigrationTests", - x => x.UseHierarchyId()); - - /// - /// Removes annotations from the model that can - /// change between versions of ef. - /// This should be called during OnModelCreating - /// - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "Uses internal efcore apis.")] - protected void RemoveVariableModelAnnotations(ModelBuilder modelBuilder) - { - var model = modelBuilder.Model; - - //the values of these could change between versions - //so get rid of them for the tests - model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); - model.RemoveAnnotation(RelationalAnnotationNames.MaxIdentifierLength); - model.RemoveAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy); - } - - public abstract string GetExpectedMigrationCode(string migrationName, string rootNamespace); - public abstract string GetExpectedSnapshotCode(string rootNamespace); + var model = modelBuilder.Model; + + //the values of these could change between versions + //so get rid of them for the tests + model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); + model.RemoveAnnotation(RelationalAnnotationNames.MaxIdentifierLength); + model.RemoveAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy); } + + public abstract string GetExpectedMigrationCode(string migrationName, string rootNamespace); + public abstract string GetExpectedSnapshotCode(string rootNamespace); } diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Migrations/TypedArraySeedContext.cs b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Migrations/TypedArraySeedContext.cs index 6b04e71c517..d9e28718acc 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Migrations/TypedArraySeedContext.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Migrations/TypedArraySeedContext.cs @@ -1,33 +1,52 @@ -using Microsoft.EntityFrameworkCore.SqlServer.Storage; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Models.Migrations +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Models.Migrations; + +internal sealed class TypedArraySeedContext : MigrationContext { - internal sealed class TypedArraySeedContext : MigrationContext + protected override void OnModelCreating(ModelBuilder modelBuilder) { - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - RemoveVariableModelAnnotations(modelBuilder); + RemoveVariableModelAnnotations(modelBuilder); - modelBuilder.Entity().HasData( - new Patriarch { Id = HierarchyId.GetRoot(), Name = "Eddard Stark" }, - new Patriarch { Id = HierarchyId.Parse("/1/"), Name = "Robb Stark" }, - new Patriarch { Id = HierarchyId.Parse("/2/"), Name = "Jon Snow" }); + modelBuilder.Entity().HasData( + new Patriarch { Id = HierarchyId.GetRoot(), Name = "Eddard Stark" }, + new Patriarch { Id = HierarchyId.Parse("/1/"), Name = "Robb Stark" }, + new Patriarch { Id = HierarchyId.Parse("/2/"), Name = "Jon Snow" }); - modelBuilder.Entity(b => + modelBuilder.Entity( + b => { b.Property(e => e.HierarchyId) .HasConversion(v => HierarchyId.Parse(v), v => v.ToString()); b.HasData( - new ConvertedPatriarch { Id = 1, HierarchyId = HierarchyId.GetRoot().ToString(), Name = "Eddard Stark" }, - new ConvertedPatriarch { Id = 2, HierarchyId = HierarchyId.Parse("/1/").ToString(), Name = "Robb Stark" }, - new ConvertedPatriarch { Id = 3, HierarchyId = HierarchyId.Parse("/2/").ToString(), Name = "Jon Snow" }); + new ConvertedPatriarch + { + Id = 1, + HierarchyId = HierarchyId.GetRoot().ToString(), + Name = "Eddard Stark" + }, + new ConvertedPatriarch + { + Id = 2, + HierarchyId = HierarchyId.Parse("/1/").ToString(), + Name = "Robb Stark" + }, + new ConvertedPatriarch + { + Id = 3, + HierarchyId = HierarchyId.Parse("/2/").ToString(), + Name = "Jon Snow" + }); }); - } + } - public override string GetExpectedMigrationCode(string migrationName, string rootNamespace) - { - return $@"using Microsoft.EntityFrameworkCore; + public override string GetExpectedMigrationCode(string migrationName, string rootNamespace) + { + return $@"using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable @@ -100,11 +119,11 @@ protected override void Down(MigrationBuilder migrationBuilder) }} }} "; - } + } - public override string GetExpectedSnapshotCode(string rootNamespace) - { - return $@"// + public override string GetExpectedSnapshotCode(string rootNamespace) + { + return $@"// using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using {ThisType.Namespace}; @@ -192,6 +211,5 @@ protected override void BuildModel(ModelBuilder modelBuilder) }} }} "; - } } } diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Patriarch.cs b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Patriarch.cs index 2e7712a4c61..080d4c3e44f 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Patriarch.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Models/Patriarch.cs @@ -1,8 +1,10 @@ -namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Models +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.SqlServer.Test.Models; + +internal class Patriarch { - class Patriarch - { - public HierarchyId Id { get; set; } - public string Name { get; set; } - } + public HierarchyId Id { get; set; } + public string Name { get; set; } } diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Utilities/FakeScaffoldingModelFactory.cs b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Utilities/FakeScaffoldingModelFactory.cs index e73193686fe..55c118a8cca 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Utilities/FakeScaffoldingModelFactory.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Utilities/FakeScaffoldingModelFactory.cs @@ -1,6 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -11,8 +11,6 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities; -#pragma warning disable EF1001 - public class FakeScaffoldingModelFactory : RelationalScaffoldingModelFactory { public FakeScaffoldingModelFactory( @@ -22,7 +20,8 @@ public FakeScaffoldingModelFactory( ICSharpUtilities cSharpUtilities, IScaffoldingTypeMapper scaffoldingTypeMapper, IModelRuntimeInitializer modelRuntimeInitializer) - : base(reporter, candidateNamingService, pluralizer, cSharpUtilities, scaffoldingTypeMapper, + : base( + reporter, candidateNamingService, pluralizer, cSharpUtilities, scaffoldingTypeMapper, modelRuntimeInitializer) { } diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Utilities/SqlServerTestHelpers.cs b/test/EFCore.SqlServer.HierarchyId.Tests/Test/Utilities/SqlServerTestHelpers.cs deleted file mode 100644 index f74e93f88ad..00000000000 --- a/test/EFCore.SqlServer.HierarchyId.Tests/Test/Utilities/SqlServerTestHelpers.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.SqlServer.Diagnostics.Internal; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore.TestUtilities; - -#pragma warning disable EF1001 - -public class SqlServerTestHelpers : TestHelpers -{ - private SqlServerTestHelpers() - { - } - - public static SqlServerTestHelpers Instance { get; } = new(); - - public override IServiceCollection AddProviderServices(IServiceCollection services) - => services.AddEntityFrameworkSqlServer(); - - public override DbContextOptionsBuilder UseProviderOptions(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseSqlServer(new SqlConnection("Database=DummyDatabase")); - - public override LoggingDefinitions LoggingDefinitions { get; } = new SqlServerLoggingDefinitions(); -} diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/TypeMappingTests.cs b/test/EFCore.SqlServer.HierarchyId.Tests/TypeMappingTests.cs index 79bfa933a51..c756affa5c8 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/TypeMappingTests.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/TypeMappingTests.cs @@ -1,56 +1,57 @@ -using System; -using Microsoft.EntityFrameworkCore.SqlServer.Storage; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; using Xunit; -namespace Microsoft.EntityFrameworkCore.SqlServer +namespace Microsoft.EntityFrameworkCore.SqlServer; + +public class TypeMappingTests { - public class TypeMappingTests + [ConditionalFact] + public void Maps_int_column() + { + var mapping = CreateMapper().FindMapping( + new RelationalTypeMappingInfo( + storeTypeName: "int", + storeTypeNameBase: "int", + unicode: null, + size: null, + precision: null, + scale: null)); + + Assert.Null(mapping); + } + + [ConditionalFact] + public void Maps_hierarchyid_column() + { + var mapping = CreateMapper().FindMapping( + new RelationalTypeMappingInfo( + storeTypeName: SqlServerHierarchyIdTypeMappingSourcePlugin.SqlServerTypeName, + storeTypeNameBase: SqlServerHierarchyIdTypeMappingSourcePlugin.SqlServerTypeName, + unicode: null, + size: null, + precision: null, + scale: null)); + + AssertMapping(mapping); + } + + private static void AssertMapping( + RelationalTypeMapping mapping) { - [Fact] - public void Maps_int_column() - { - var mapping = CreateMapper().FindMapping( - new RelationalTypeMappingInfo( - storeTypeName: "int", - storeTypeNameBase: "int", - unicode: null, - size: null, - precision: null, - scale: null)); - - Assert.Null(mapping); - } - - [Fact] - public void Maps_hierarchyid_column() - { - var mapping = CreateMapper().FindMapping( - new RelationalTypeMappingInfo( - storeTypeName: SqlServerHierarchyIdTypeMappingSourcePlugin.SqlServerTypeName, - storeTypeNameBase: SqlServerHierarchyIdTypeMappingSourcePlugin.SqlServerTypeName, - unicode: null, - size: null, - precision: null, - scale: null)); - - AssertMapping(mapping); - } - - private static void AssertMapping( - RelationalTypeMapping mapping) - { - AssertMapping(typeof(T), mapping); - } - - private static void AssertMapping( - Type type, - RelationalTypeMapping mapping) - { - Assert.Same(type, mapping.ClrType); - } - - private static IRelationalTypeMappingSourcePlugin CreateMapper() - => new SqlServerHierarchyIdTypeMappingSourcePlugin(); + AssertMapping(typeof(T), mapping); } + + private static void AssertMapping( + Type type, + RelationalTypeMapping mapping) + { + Assert.Same(type, mapping.ClrType); + } + + private static IRelationalTypeMappingSourcePlugin CreateMapper() + => new SqlServerHierarchyIdTypeMappingSourcePlugin(); } diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/WrapperTests.cs b/test/EFCore.SqlServer.HierarchyId.Tests/WrapperTests.cs index c6a7c62a304..475aacbbdab 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/WrapperTests.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/WrapperTests.cs @@ -1,15 +1,38 @@ -using Xunit; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.SqlServer +using Xunit; + +namespace Microsoft.EntityFrameworkCore.SqlServer; + +public class WrapperTests { - public class WrapperTests - { - [Fact] - public void GetAncestor_returns_null_when_too_high() - => Assert.Null(HierarchyId.Parse("/1/").GetAncestor(2)); - - [Fact] - public void GetReparentedValue_returns_null_when_newRoot_is_null() - => Assert.Null(HierarchyId.Parse("/1/").GetReparentedValue(HierarchyId.GetRoot(), newRoot: null)); - } + [ConditionalTheory] + [InlineData(null, 1)] + [InlineData("/", 1)] + [InlineData("/0.5/", 1)] + [InlineData("/1/", 0)] + [InlineData("/2/", -1)] + [InlineData("/1/1/", -1)] + public void CompareTo_works(string value, int expected) + => Assert.Equal(expected, HierarchyId.Parse("/1/").CompareTo(HierarchyId.Parse(value))); + + [ConditionalTheory] + [InlineData(null, false)] + [InlineData("/", false)] + [InlineData("/1/", true)] + public void Equals_works(string value, bool expected) + => Assert.Equal(expected, HierarchyId.Parse("/1/").Equals(HierarchyId.Parse(value))); + + [ConditionalFact] + public void GetAncestor_returns_null_when_too_high() + => Assert.Null(HierarchyId.Parse("/1/").GetAncestor(2)); + + [ConditionalFact] + public void GetReparentedValue_returns_null_when_newRoot_is_null() + => Assert.Null(HierarchyId.Parse("/1/").GetReparentedValue(HierarchyId.GetRoot(), newRoot: null)); + + [ConditionalFact] + public void IsDescendantOf_returns_false_when_parent_is_null() + => Assert.False(HierarchyId.Parse("/1/").IsDescendantOf(null)); }