Giter Club home page Giter Club logo

efcore.sqlserver.hierarchyid's Introduction

EntityFrameworkCore.SqlServer.HierarchyId

build status latest version downloads license

Adds hierarchyid support to the SQL Server EF Core provider.

Moved to EF Core

This project has been merged into dotnet/efcore. All future bug fixes, enhancements, and releases will be done as part of the main EF Core project.

Installation

The latest stable version is available on NuGet.

dotnet add package EntityFrameworkCore.SqlServer.HierarchyId

Compatibility

The following table show which version of this library to use with which version of EF Core.

EF Core Version to use
7.0 4.x
6.0 3.x
3.1 1.x

Usage

Enable hierarchyid support by calling UseHierarchyId inside UseSqlServer. UseSqlServer is is typically called inside Startup.ConfigureServices or OnConfiguring of your DbContext type.

options.UseSqlServer(
    connectionString,
    x => x.UseHierarchyId());

Add HierarchyId properties to your entity types.

class Patriarch
{
    public HierarchyId Id { get; set; }
    public string Name { get; set; }
}

Insert data.

dbContext.AddRange(
    new Patriarch { Id = HierarchyId.GetRoot(), Name = "Abraham" },
    new Patriarch { Id = HierarchyId.Parse("/1/"), Name = "Isaac" },
    new Patriarch { Id = HierarchyId.Parse("/1/1/"), Name = "Jacob" });
dbContext.SaveChanges();

Query.

var thirdGeneration = from p in dbContext.Patriarchs
                      where p.Id.GetLevel() == 2
                      select p;

Testing

A package for the In-Memory EF Core provider is also available to enable unit testing components that consume HierarchyId data.

dotnet add package EntityFrameworkCore.InMemory.HierarchyId
options.UseInMemoryDatabase(
    databaseName,
    x => x.UseHierarchyId());

See also

efcore.sqlserver.hierarchyid's People

Contributors

ajcvickers avatar aljones avatar bricelam avatar cutig3r avatar dependabot[bot] avatar huan086 avatar kmataru avatar mehdihaghshenas avatar vyrotek avatar yuryjhol avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

efcore.sqlserver.hierarchyid's Issues

Support for Microsoft.EntityFrameworkCore.InMemory?

Hi,

Is somehow possible to unit test app with entities using HierarchyId type properties?

I have added a prop of HierarchyId type to one my entitties, running project and I got error:
System.InvalidOperationException : No suitable constructor found for entity type 'HierarchyId'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'value' in 'HierarchyId(SqlHierarchyId value)'.

I have tried to use .UseHierarchyId() with memory database configuration but the method is supposed only for SqlServerDbContextOptionsBuilder, as expected.

Similar to this issue: bricelam#2

Project to demonstrate this issuse: https://github.com/saliksaly/aspnetcoreapp-efcore-inmemory-EFCore.SqlServer.HierarchyId
Just get and run.

Thanks for your help...

Issues with specific values

There is an issue with specific values. Using the following code in linqpad:

var parsed = HierarchyId.Parse("/1.3.2/").Dump("parsed");
var ms = new MemoryStream();
parsed.Write(new BinaryWriter(ms));
ms.Position = 0;
var roundTrip = HierarchyId.Read(new BinaryReader(ms)).Dump("roundTrip");
Input Value of parsed Value of roundTrip
/1.0.2/ /1.0.2/ /1.0.2/
/1.1.2/ /1.1.2/ /1.1.2/
/1.2.2/ /1.2.2/ /1.2.2/
/1.3.2/ /1.3.2/ InvalidCastException: No pattern found for: 11010
/3.0/ /3.0/ /

Using Microsoft.SqlServer.Types.SqlHierarchyId (Microsoft.SqlServer.Types, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null) directly in netcore31 (linqpad6) produces the same bad result.

Using Microsoft.SqlServer.Types.SqlHierarchyId (Microsoft.SqlServer.Types, Version=13.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91) directly in net46+ (linqpad5) works with all tested values as expected.

EF Core Migration Support

When trying to create a migration, I get the following error:

The current CSharpHelper cannot scaffold literals of type 'System.Data.SqlTypes.SqlBytes'. Configure your services to use one that can.

I see that from another issue, you mentioned that the GenerateCodeLiteral method needs to be overridden: #1975.

I inspected the source and it appears that this has not been overridden. See here.

I'd be happy to create a PR for this if that would help. Just let me know. Thanks!

Critical Bug with HierarchyId Serialization/Deserialization

We ran into a show-stopping issue with the writing and reading of HierarchyId binary data.

If findings below are accurate then folks need to know that this wonderful library is not production ready at the moment!
V 2.1.0 now pulls in V 1.3.0 of dotMorten which addresses this specific bug. Thanks everyone!

This library uses dotMorten/Microsoft.SqlServer.Types as the backing SqlHierarchyId implementation which has an outstanding bug with how it reads and writes Id values.

For example: Using GetDescendant on SQL Server generates the following:

declare @h HIERARCHYID = '/1/1/'
select
[Binary] = @h.GetDescendant('/1/1/3/', '/1/1/4/'),
[String] = @h.GetDescendant('/1/1/3/', '/1/1/4/').ToString()

Binary		String
0x5AE058	/1/1/3.1/

But in C# it generates the following invalid binary value:

var newId = HierarchyId.Parse("/1/1/").GetDescendant(HierarchyId.Parse("/1/1/3/"), HierarchyId.Parse("/1/1/4/"));

using (var ms = new MemoryStream())
using (var binWriter = new BinaryWriter(ms))
{
    newId.Write(binWriter);
    var byteString = BitConverter.ToString(ms.ToArray()).Replace("-", "");
    var result = String.Format("0x{0}", byteString);
}

// Result = 0x5AE0B0

Here are some related issues and PRs:
dotMorten/Microsoft.SqlServer.Types#35
dotMorten/Microsoft.SqlServer.Types#55

I'm no expert, but after reading through this article and the PRs above it seems maybe the issue occurs when you try to use any Id value which is on the end of the special ranges? (ex. 0 through 3, 4 through 7, etc.)
http://www.adammil.net/blog/v100_how_the_SQL_Server_hierarchyid_data_type_works_kind_of_.html

I also verified that Using the .NET Framework version of Microsoft.SqlServer.Types does not have the issue.

I'm not sure what options are available until this is resolved. The maintainer of the dotMorten library has not committed to getting this resolved and does not seem to feel confident in maintaining HierarchyId features in his library. In fact, he may remove it completely.
dotMorten/Microsoft.SqlServer.Types#55 (comment)

System.ArgumentException thrown when EnableRetryOnFailure is used.

Steps to reproduce the bug
The following test reproduces the issue:

HierarchyIdRetry.csproj

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netcoreapp3.1</TargetFramework>
        <LangVersion>latest</LangVersion>
        <Nullable>enable</Nullable>
        <IsPackable>false</IsPackable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
        <PackageReference Include="xunit" Version="2.4.1" />
        <PackageReference Include="EntityFrameworkCore.SqlServer.HierarchyId" Version="1.1.0" />
        <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.7" />
    </ItemGroup>
</Project>

FixtureRetryOnFailure.cs

using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Xunit;

namespace HierarchyIdRetry
{
    public class TestEntity
    {
        public TestEntity(string id, HierarchyId position)
        {
            Id = id;
            Position = position;
        }

        public string Id { get; }
        public HierarchyId Position { get; }
    }

    public class TestDbContext : DbContext
    {
        public TestDbContext(DbContextOptions<TestDbContext> options)
            : base(options)
        {
        }

        public virtual DbSet<TestEntity> Entities { get; set; } = default!;

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<TestEntity>(entity =>
            {
                entity.HasKey(e => new { e.Id });
                entity.Property(e => e.Position);
            });
        }
    }

    public class FixtureRetryOnFailure
    {
        public const string ConnectionString = "Data Source=localhost;Initial Catalog=HierarchyId;Integrated Security=True";

        [Theory]
        [InlineData(false)]
        [InlineData(true)]
        public async Task TestRetryOnFailure(bool enableRetryOnFailure)
        {
            var optionsBuilder = new DbContextOptionsBuilder<TestDbContext>()
                .UseSqlServer(ConnectionString, o =>
                {
                    o.UseHierarchyId();
                    if (enableRetryOnFailure)
                    {
                        o.EnableRetryOnFailure(10);
                    }
                });

            await using var dbContext = new TestDbContext(optionsBuilder.Options);

            await dbContext.Database.EnsureDeletedAsync();
            await dbContext.Database.EnsureCreatedAsync();

            await dbContext.Entities.AddAsync(new TestEntity("Hello", HierarchyId.GetRoot()));
            await dbContext.SaveChangesAsync();

            var loaded = await dbContext.Entities.FirstAsync(e => e.Id == "Hello");
            Assert.Equal(HierarchyId.GetRoot(), loaded.Position);
        }
    }
}

Resulting and expected behavior
The expected behavior is the tests to pass. Instead an exception is thrown, when EnableRetryOnFailure is enabled.

System.ArgumentException
Expression of type 'System.Object' cannot be used for parameter of type 'System.Data.SqlTypes.SqlBytes' 
of method 'Microsoft.EntityFrameworkCore.HierarchyId fromProvider(System.Data.SqlTypes.SqlBytes)' (Parameter 'arg0')

TypeLoadException in SqlServerHierarchyIdMethodTranslator with EF Core 5.0.0-rc.1

I was experimenting with updating our app to use EF Core 5.0.0-rc.1.20451.13 but got this exception from the DbContext.

System.TypeLoadException: 'Method 'Translate' in type 'Microsoft.EntityFrameworkCore.SqlServer.Query.ExpressionTranslators.SqlServerHierarchyIdMethodTranslator' from assembly 'EntityFrameworkCore.SqlServer.HierarchyId, Version=1.1.0.0, Culture=neutral, PublicKeyToken=0c30ba8633cb7b1e' does not have an implementation.'

I'm not quite sure what other info I can provide to help. :)

Dependency update and next release

Do we need to bump the version to 5.0 in our dependencies? Does the code we have work for both versions?

Depending on answer of above we can decide when to ship a new version.

Cannot serialize/deserialize object without parameterless constructor

I've started using the HierarchyId for a Blazor project I've started and it's was failing as it's use Json for serialize/deserialize object and HierarchyId doesn't have one. I've create a simple converter to perform the task. I'm not sure if this should belong in this repository or not.

using Microsoft.EntityFrameworkCore;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ANamespace
{
    public class HierarchyIdConverter : JsonConverter<HierarchyId>
    {
        public override HierarchyId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return HierarchyId.Parse(reader.GetString());
        }

        public override void Write(Utf8JsonWriter writer, HierarchyId value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.ToString());
        }
    }
}

InvalidCastException when trying to map SQL Server File Table to entity

Hi

I have installed this package, and created a class that corresponds to the File Table

 public class DocumentStore
  {
    [Column("stream_id")]
    public Guid StreamId { get; set; }

    [Column("file_stream")]
    public byte[]? FileStream { get; set; }

    [Column("name")]
    [StringLength(255)]
    public string Name { get; set; }

    [Column("path_locator")]
    public HierarchyId PathLocator { get; set; }

    [Column("parent_path_locator")]
    public HierarchyId? ParentPathLocator { get; set; }
    ...
    ...
  }

But when I try to query the table I get:

System.InvalidCastException: No pattern found for: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101101010110101011000100000
   at Microsoft.SqlServer.Types.SqlHierarchy.KnownPatterns.GetPatternByPrefix(BitReader bitR)
   at Microsoft.SqlServer.Types.SqlHierarchyId.Read(BinaryReader r)
   at Microsoft.EntityFrameworkCore.HierarchyId.Read(BinaryReader reader)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.SqlServerHierarchyIdValueConverter.fromProvider(SqlBytes bytes)
   at lambda_method9(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at BrandManagement.Persistence.Repository.BrandRepository.GetDocumentStoreAsync() in C:\dev\git\product-unik_document_server\src\BrandManagement\BrandManagement.Persistence\Repository\BrandRepository.cs:line 33
   at BrandManagement.Application.Service.BrandService.GetDSAsync() in C:\dev\git\product-unik_document_server\src\BrandManagement\BrandManagement.Application\Service\BrandService.cs:line 33
   at BrandManagement.WebApi.v1.BrandController.GetDSAsync() in C:\dev\git\product-unik_document_server\src\BrandManagement\BrandManagement.WebApi\v1\BrandController.cs:line 46
   at lambda_method4(Closure , Object )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

If I comment out PathLocator and ParentPathLocator, it works just fine. But I need to use these fields in order to save in folders.

Any suggestions?
Thank

This Package Has Error in EFCore 7 (7.0.0-preview.7.22376.2)

When we want to add HierarchyId into EF Core 7 we get the below Error:

System.MissingMethodException: 'Method not found: 'Void CoreTypeMappingParameters..ctor(System.Type, Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter, Microsoft.EntityFrameworkCore.ChangeTracking.ValueComparer, Microsoft.EntityFrameworkCore.ChangeTracking.ValueComparer, System.Func`3<Microsoft.EntityFrameworkCore.Metadata.IProperty,Microsoft.EntityFrameworkCore.Metadata.IEntityType,Microsoft.EntityFrameworkCore.ValueGeneration.ValueGenerator>)'.'



   at Microsoft.EntityFrameworkCore.SqlServer.Storage.SqlServerHierarchyIdTypeMapping.CreateRelationalTypeMappingParameters(String storeType, Type clrType)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.SqlServerHierarchyIdTypeMappingSourcePlugin.FindMapping(RelationalTypeMappingInfo& mappingInfo)
   at Microsoft.EntityFrameworkCore.Storage.RelationalTypeMappingSource.FindMapping(RelationalTypeMappingInfo& mappingInfo)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerTypeMappingSource.FindMapping(RelationalTypeMappingInfo& mappingInfo)
   at Microsoft.EntityFrameworkCore.Storage.RelationalTypeMappingSource.<FindMappingWithConversion>b__8_0(ValueTuple`3 k)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.EntityFrameworkCore.Storage.RelationalTypeMappingSource.FindMapping(Type type, IModel model)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.MemberClassifier.IsCandidateNavigationPropertyType(Type targetType, MemberInfo memberInfo, Model model, Nullable`1& shouldBeOwned)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.MemberClassifier.FindCandidateNavigationPropertyType(MemberInfo memberInfo, IConventionModel model, Nullable`1& shouldBeOwned)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.MemberClassifier.GetNavigationCandidates(IConventionEntityType entityType)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.RelationshipDiscoveryConvention.FindRelationshipCandidates(IConventionEntityTypeBuilder entityTypeBuilder, HashSet`1 otherInverseCandidateTypes)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.RelationshipDiscoveryConvention.DiscoverRelationships(IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context, HashSet`1 otherInverseCandidateTypes)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.OnEntityTypeAddedNode.Run(ConventionDispatcher dispatcher)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.DelayedConventionScope.Run(ConventionDispatcher dispatcher)
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ConventionBatch.Run()
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ConventionBatch.Dispose()
   at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelInitialized(IConventionModelBuilder modelBuilder)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.Model..ctor(ConventionSet conventions, ModelDependencies modelDependencies, ModelConfiguration modelConfiguration)
   at Microsoft.EntityFrameworkCore.ModelBuilder..ctor(ConventionSet conventions, ModelDependencies modelDependencies, ModelConfiguration modelConfiguration)
   at Microsoft.EntityFrameworkCore.ModelConfigurationBuilder.CreateModelBuilder(ModelDependencies modelDependencies)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, IConventionSetBuilder conventionSetBuilder, ModelDependencies modelDependencies)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime)
   at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.Set[TEntity]()

SqlServerHierarchyIdTypeMappingSourcePlugin.FindMapping returns wrong type mapping when mappingInfo.ClrType is null

fix:
public virtual RelationalTypeMapping FindMapping(in RelationalTypeMappingInfo mappingInfo)
{
var clrType = mappingInfo.ClrType/* ?? typeof(HierarchyId)*/;
var storeTypeName = mappingInfo.StoreTypeName;

        return typeof(HierarchyId).IsAssignableFrom(clrType) || storeTypeName == SqlServerTypeName
            ? new SqlServerHierarchyIdTypeMapping(SqlServerTypeName, clrType ?? typeof(HierarchyId))
            : null;
    }

See how the same logic realized in SqlServerNetTopologySuiteTypeMappingSourcePlugin.

IndexOutOfRangeException: When calling GetDescendant

I try to get a hierarchy id between two other ids.

When I do:

	var hier = HierarchyId.Parse("/2/");

	var h1 = HierarchyId.Parse("/2/1/");
	var h2 = HierarchyId.Parse("/2/2/");

	var h1a = hier.GetDescendant(h1, h2); // -> /2/1.1/
	var h1aa = hier.GetDescendant(h1, h1a); // -> /2/1.0/    -------- this throws exception
	var h1aaa = hier.GetDescendant(h1aa, h1a); // -> /2/1.0.1/

Then I'm getting an exception.

I was pretty certain that it should work so I tried the same in MS SQL

DECLARE @h HIERARCHYID;
DECLARE @h1 HIERARCHYID;
DECLARE @h2 HIERARCHYID;
DECLARE @h1a HIERARCHYID;
DECLARE @h1aa HIERARCHYID;
DECLARE @h1aaa HIERARCHYID;

SET @h = '/2/';
SET @h1 = '/2/1/';
SET @h2 = '/2/2/';
SET @h1a = @h.GetDescendant(@h1, @h2);
SET @h1aa = @h.GetDescendant(@h1, @h1a);
SET @h1aaa = @h.GetDescendant(@h1aa, @h1a);

SELECT @h1aa.ToString()   --/2/1.0/
SELECT @h1aaa.ToString() --/2/1.0.1/

And it works there

Navigation property support

Hi,
Thanks for all your work.

Do you know a possibility to get navigation properties to work with HirachyId?

    class Patriarch
    {
        public HierarchyId Id { get; set; }
        public string Name { get; set; }
        public Patriarch Father{ get; set; }
    }

with

modelBuilder.Entity<Patriach>()
                .HasOne(x => x.Father)
                .WithMany()
                .HasForeignKey(x => x.Id.GetAncestor(1));

complains about the property expression.

The properties expression 'x => x.Id.GetAncestor(1)' is not valid. The expression should represent a simple property access: 't => t.MyProperty'. When specifying multiple properties use an anonymous type: 't => new { t.MyProperty1, t.MyProperty2 }'.

due to my limited knowledge about the ef core internals, I was not able to work out a solution on my own.

Thanks,
Robert

Cant select a whole item with a HierarchyId field

When I query an object with a HierarchyId field I get

Expression of type 'System.Object' cannot be used for parameter of type 'System.Data.SqlTypes.SqlBytes' of method 'Microsoft.EntityFrameworkCore.HierarchyId fromProvider(System.Data.SqlTypes.SqlBytes)'

If I select just some other properties or transform the object with the HierarchyId as string it works

This issue makes difficult to update entities with HierarchyId props

"Microsoft.EntityFrameworkCore.Tools" Version="3.1.9"
"Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.9"
"EntityFrameworkCore.SqlServer.HierarchyId" Version="1.1.1"

TargetFramework: netcoreapp3.1

Insert Data With Order

Hello, this is my Comment Entity.

public class Comment
{
    public long CommentId { get; set; }
    public HierarchyId CommentHierarchyId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Content { get; set; }
    public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.Now;
    public DateTimeOffset DateModified { get; set; } = DateTimeOffset.Now;
}

and I use this Dto to create a comment.

public record CreateCommentDto
{
    [HiddenInput]
    public long ParentId { get; init; }
    public string FirstName { get; init; }
    public string LastName { get; init; }
    public string Email { get; init; }
    public string Content { get; init; }
}

I use this code to insert a comment but it's not ordered, I don't know to use which method?

Comment newComment = new()
{
    Email = createCommentDto.Email,
    FirstName = createCommentDto.FirstName,
    LastName = createCommentDto.LastName,
    Content = createCommentDto.Content,
};

if (createCommentDto.ParentId > 0)
{
    Comment parentComment = await _db.Comments.FindAsync(createCommentDto.ParentId);


    newComment.CommentHierarchyId =  parentComment.CommentHierarchyId.GetDescendant(null,null);
 /* I think these above line need to change. */
}
else
{
   newComment.CommentHierarchyId = HierarchyId.GetRoot();
}

await _db.Comments.AddAsync(newComment);
await _db.SaveChangesAsync();
return RedirectToAction(nameof(Index));

CommentsTable

HierarchyId class: Equality & Inequality & Comparison not taking nulls into consideration (due being a class)

This causes issues when using the max linq iqueryable exension.
Consider the following scenario:

HierarchyId nullHid = null;
HierarchyId root = HierarchyId.GetRoot();

if (root == nullHid || root.Equals(nullHid) || Equals(root, nullHid) || nullHid == root || Equals(nullHid, root)) {
    Console.WriteLine("Wrong");
}

Additionally, nulls should be less than real values
Consider the following scenario:

HierarchyId nullHid = null;
HierarchyId root = HierarchyId.GetRoot();

if (root < nullHid || nullHid > root) {
    Console.WriteLine("Wrong");
}

I've tested the following and it appears to function

using System;
using System.IO;
using Microsoft.SqlServer.Types;

namespace Microsoft.EntityFrameworkCore
{
    /// <summary>
    /// Represents a position in a hierarchical structure, specifying depth and breadth.
    /// </summary>
    public class HierarchyId : IComparable
    {
        private SqlHierarchyId _value;

        private HierarchyId(SqlHierarchyId value)
        {
            if (value.IsNull)
                throw new ArgumentNullException(nameof(value));

            _value = value;
        }

        /// <summary>
        /// Gets the root node of the hierarchy.
        /// </summary>
        /// <returns>The root node of the hierarchy.</returns>
        public static HierarchyId GetRoot()
            => new HierarchyId(SqlHierarchyId.GetRoot());

        /// <summary>
        /// Converts the canonical string representation of a node to a <see cref="HierarchyId"/> value.
        /// </summary>
        /// <param name="input">The string representation of a node.</param>
        /// <returns>A <see cref="HierarchyId"/> value.</returns>
        public static HierarchyId Parse(string input)
            => Wrap(SqlHierarchyId.Parse(input));

        /// <summary>
        /// Reads a <see cref="HierarchyId"/> value from the specified reader.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns>A <see cref="HierarchyId"/> value.</returns>
        public static HierarchyId Read(BinaryReader reader)
        {
            var hid = new SqlHierarchyId();
            hid.Read(reader);
            return Wrap(hid);
        }

        /// <summary>
        /// Writes this <see cref="HierarchyId"/> value to the specified writer.
        /// </summary>
        /// <param name="writer">The writer.</param>
        public void Write(BinaryWriter writer)
        {
            _value.Write(writer);
        }

        /// <inheritdoc/>
        public int CompareTo(object obj)
            => _value.CompareTo(
                    obj is HierarchyId other
                        ? other._value
                        : obj);

        /// <inheritdoc/>
        public override bool Equals(object obj)
            => _value.Equals(
                    obj is HierarchyId other
                        ? other._value
                        : obj);

        /// <summary>
        /// Gets the node <paramref name="n"/> levels up the hierarchical tree.
        /// </summary>
        /// <param name="n">The number of levels to ascend in the hierarchy.</param>
        /// <returns>A <see cref="HierarchyId"/> value represengint the <paramref name="n"/>th ancestor of this node or null if <paramref name="n"/> is greater than <see cref="GetLevel"/>.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="n"/> is negative.</exception>
        public HierarchyId GetAncestor(int n)
            => new HierarchyId(_value.GetAncestor(n));

        /// <summary>
        /// Gets the value of a descendant node that is greater than <paramref name="child1"/> and less than <paramref name="child2"/>.
        /// </summary>
        /// <param name="child1">The lower bound.</param>
        /// <param name="child2">The upper bound.</param>
        /// <returns>A <see cref="HierarchyId"/> value.</returns>
        public HierarchyId GetDescendant(HierarchyId child1, HierarchyId child2)
            => Wrap(_value.GetDescendant(Unwrap(child1), Unwrap(child2)));

        /// <inheritdoc/>
        public override int GetHashCode()
            => _value.GetHashCode();

        /// <summary>
        /// Gets the level of this node in the hierarchical tree.
        /// </summary>
        /// <returns>The depth of this node. The root node is level 0.</returns>
        public short GetLevel()
            => _value.GetLevel().Value;

        /// <summary>
        /// Gets a value representing the location of a new node that has a path from <paramref name="newRoot"/> equal to the path from <paramref name="oldRoot"/> to this, effectively moving this to the new location.
        /// </summary>
        /// <param name="oldRoot">An ancestor of this node specifying the endpoint of the path segment to be moved.</param>
        /// <param name="newRoot">The node that represents the new ancestor.</param>
        /// <returns>A <see cref="HierarchyId"/> value or null if <paramref name="oldRoot"/> or <paramref name="newRoot"/> is null.</returns>
        public HierarchyId GetReparentedValue(HierarchyId oldRoot, HierarchyId newRoot)
            => new HierarchyId(_value.GetReparentedValue(Unwrap(oldRoot), Unwrap(newRoot)));

        /// <summary>
        /// Gets a value indicating whether this node is a descendant of <paramref name="parent"/>.
        /// </summary>
        /// <param name="parent">The parent to test against.</param>
        /// <returns>True if this node is in the sub-tree rooted at <paramref name="parent"/>; otherwise false.</returns>
        public bool IsDescendantOf(HierarchyId parent)
        {
            if (parent == null)
                return false;

            return _value.IsDescendantOf(parent._value).Value;
        }

        /// <inheritdoc/>
        public override string ToString()
            => _value.ToString();

        /// <summary>
        /// Evaluates whether two nodes are equal.
        /// </summary>
        /// <param name="hid1">The first node to compare.</param>
        /// <param name="hid2">The second node to compare.</param>
        /// <returns>True if <paramref name="hid1"/> and <paramref name="hid2"/> are equal; otherwise, false.</returns>
        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;
        }

        /// <summary>
        /// Evaluates whether two nodes are unequal.
        /// </summary>
        /// <param name="hid1">The first node to compare.</param>
        /// <param name="hid2">The second node to compare.</param>
        /// <returns>True if <paramref name="hid1"/> and <paramref name="hid2"/> are unequal; otherwise, false.</returns>
        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;
        }

        /// <summary>
        /// Evaluates whether one node is less than another.
        /// </summary>
        /// <param name="hid1">The first node to compare.</param>
        /// <param name="hid2">The second node to compare.</param>
        /// <returns>True if <paramref name="hid1"/> is less than <paramref name="hid2"/>; otherwise, false.</returns>
        public static bool operator <(HierarchyId hid1, HierarchyId hid2)
        {
            var sh1 = Unwrap(hid1);
            var sh2 = Unwrap(hid2);

            if (sh1.IsNull == sh2.IsNull)
                return sh1.CompareTo(sh2) < 0;
            else if (sh1.IsNull)
                return true;

            return false;
        }

        /// <summary>
        /// Evaluates whether one node is greater than another.
        /// </summary>
        /// <param name="hid1">The first node to compare.</param>
        /// <param name="hid2">The second node to compare.</param>
        /// <returns>True if <paramref name="hid1"/> is greather than <paramref name="hid2"/>; otherwise, false.</returns>
        public static bool operator >(HierarchyId hid1, HierarchyId hid2)
        {
            var sh1 = Unwrap(hid1);
            var sh2 = Unwrap(hid2);

            if (sh1.IsNull == sh2.IsNull)
                return sh1.CompareTo(sh2) > 0;
            else if (sh1.IsNull)
                return false;

            return true;
        }

        /// <summary>
        /// Evaluates whether one node is less than or equal to another.
        /// </summary>
        /// <param name="hid1">The first node to compare.</param>
        /// <param name="hid2">The second node to compare.</param>
        /// <returns>True if <paramref name="hid1"/> is less than or equal to <paramref name="hid2"/>; otherwise, false.</returns>
        public static bool operator <=(HierarchyId hid1, HierarchyId hid2)
            => Unwrap(hid1).CompareTo(Unwrap(hid2)) <= 0;

        /// <summary>
        /// Evaluates whether one node is greater than or equal to another.
        /// </summary>
        /// <param name="hid1">The first node to compare.</param>
        /// <param name="hid2">The second node to compare.</param>
        /// <returns>True if <paramref name="hid1"/> is greater than or equal to <paramref name="hid2"/>; otherwise, false.</returns>
        public static bool operator >=(HierarchyId hid1, HierarchyId hid2)
            => Unwrap(hid1).CompareTo(Unwrap(hid2)) >= 0;

        private static SqlHierarchyId Unwrap(HierarchyId value)
            => value?._value ?? SqlHierarchyId.Null;

        private static HierarchyId Wrap(SqlHierarchyId value)
            => value.IsNull ? null : new HierarchyId(value);
    }
}

Build issues with Xamarin.Forms

Hi,
thanks for the great library that works like a sharm on WPF. But if I want to use the a library that is used on WPF and Xamarin.Forms togethen then i get the following exception:

##[error]C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Xamarin\Android\Xamarin.Android.Common.targets(1942,5): Error XALNK7000: Java.Interop.Tools.Diagnostics.XamarinAndroidException: error XA2006: Could not resolve reference to 'System.Void Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute::.ctor(Microsoft.SqlServer.Server.Format)' (defined in assembly 'Microsoft.SqlServer.Types, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null') with scope 'System.Data.SqlClient, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. When the scope is different from the defining assembly, it usually means that the type is forwarded. ---> Mono.Cecil.ResolutionException: Failed to resolve System.Void Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute::.ctor(Microsoft.SqlServer.Server.Format)

Since EntityFrameworkCore.SqlServer.HierarchyId.Abstractions reference Microsoft.SqlServer.Types only on .NETFramework 4.6.1 is seems that on Xamarin the wrong dll is linked.

<PropertyGroup>
  <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
  <PackageReference Include="EntityFrameworkCore.SqlServer.HierarchyId" Version="1.0.0" />
</ItemGroup>

The error occurs only when building with AzureDevops. Locally there is no issue.

HasConversion fails on DB write with DbUpdateException (InvalidCastException)

Imagine I want to have hierarchyid as a string on my model. I could do this inside OnModelCreating:

modelBuilder
  .Entity<MyEntity>()
  .Property(x => x.HierId)
  .HasConversion(str => HierarchyId.Parse(str), id => id.ToString());

The reading part works fine, but writing the value back to DB throws an error:

Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
---> System.InvalidCastException: Invalid cast from 'System.String' to 'Microsoft.EntityFrameworkCore.HierarchyId'.
at System.Convert.DefaultToType(IConvertible value, Type targetType, IFormatProvider provider)
at System.String.System.IConvertible.ToType(Type type, IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType)
at Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter`2.Sanitize[T](Object value)
at Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter`2.<>c__DisplayClass3_0`2.b__0(Object v)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.SqlServerHierarchyIdTypeMapping.CreateParameter(DbCommand command, String name, Object value, Nullable`1 nullable)
at Microsoft.EntityFrameworkCore.Storage.Internal.TypeMappedRelationalParameter.AddDbParameter(DbCommand command, Object value)
at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalParameterBase.AddDbParameter(DbCommand command, IReadOnlyDictionary`2 parameterValues)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.CreateDbCommand(RelationalCommandParameterObject parameterObject, Guid commandId, DbCommandMethod commandMethod)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(DbContext _, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
---> System.InvalidCastException: Invalid cast from 'System.String' to 'Microsoft.EntityFrameworkCore.HierarchyId'.
at System.Convert.DefaultToType(IConvertible value, Type targetType, IFormatProvider provider)
at System.String.System.IConvertible.ToType(Type type, IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType)
at Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter`2.Sanitize[T](Object value)
at Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter`2.<>c__DisplayClass3_0`2.b__0(Object v)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.SqlServerHierarchyIdTypeMapping.CreateParameter(DbCommand command, String name, Object value, Nullable`1 nullable)
at Microsoft.EntityFrameworkCore.Storage.Internal.TypeMappedRelationalParameter.AddDbParameter(DbCommand command, Object value)
at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalParameterBase.AddDbParameter(DbCommand command, IReadOnlyDictionary`2 parameterValues)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.CreateDbCommand(RelationalCommandParameterObject parameterObject, Guid commandId, DbCommandMethod commandMethod)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(DbContext _, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)

I've checked the base implementation of RelationalTypeMapping.CreateParameter() and it looks like it's supposed to call the converter before binding the parameter's value: L495-L498, which the current implementation of SqlServerHierarchyIdTypeMapping.CreateParameter() lacks.

Here's my proposed fix:

--- a/EFCore.SqlServer.HierarchyId/Storage/SqlServerHierarchyIdTypeMapping.cs
+++ b/EFCore.SqlServer.HierarchyId/Storage/SqlServerHierarchyIdTypeMapping.cs
@@ -92,9 +92,15 @@ public override DbParameter CreateParameter(DbCommand command, string name, obje
             parameter.Direction = ParameterDirection.Input;
             parameter.ParameterName = name;

-            parameter.Value = value == null
-                ? DBNull.Value
-                : _valueConverter.ConvertToProvider(value);
+            if (value != null)
+            {
+                if (Converter != null)
+                    value = Converter.ConvertToProvider(value);
+
+                value = _valueConverter.ConvertToProvider(value);
+            }
+
+            parameter.Value = value ?? DBNull.Value;

Leverage IInMemoryDbContextOptionsBuilderInfrastructure

After upgrading to EF Core 6, we can remove the reflection code in InMemoryHierarchyIdDbContextOptionsBuilderExtensions.

// Work around dotnet/efcore#23669
var optionsBuilderPropertyInfo = optionsBuilder.GetType().GetProperty("OptionsBuilder", BindingFlags.Instance | BindingFlags.NonPublic);
var coreOptionsBuilder = optionsBuilderPropertyInfo.GetValue(optionsBuilder) as DbContextOptionsBuilder;

Can't get it to work with a FileTable

I've got a SQL Server FileTable in a SQL Server 2012 database, which has a hierarchyid as the primary key. I'm trying to access the table, and get the following error.

info: EfCoreHierarchyIdTest.Worker[0]
      Worker running at: 06/11/2020 16:18:11 -07:00
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 3.1.5 initialized 'JupiterContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: using HierarchyId
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (76ms) [Parameters=[@__guid_0='?' (DbType = Guid)], CommandType='Text', CommandTimeout='30']
      SELECT [s].[path_locator], [s].[cached_file_size], [s].[creation_time], [s].[file_stream], [s].[is_archive], [s].[is_directory], [s].[is_hidden], [s].[is_offline], [s].[is_readonly], [s].[is_system], [s].[is_temporary], [s].[last_access_time], [s].[last_write_time], [s].[name], [s].[parent_path_locator], [s].[stream_id]
      FROM [5Binaries].[StoredFileTableData] AS [s]
      WHERE [s].[stream_id] = @__guid_0
fail: Microsoft.EntityFrameworkCore.Query[10100]
      An exception occurred while iterating over the results of a query for context type 'EfCoreHierarchyIdTest.EfContext.JupiterContext'.
      System.InvalidCastException: No pattern found for: 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010100110010001001101111110000000
         at Microsoft.SqlServer.Types.SqlHierarchy.KnownPatterns.GetPatternByPrefix(BitReader bitR)
         at Microsoft.SqlServer.Types.SqlHierarchyId.Read(BinaryReader r)
         at Microsoft.EntityFrameworkCore.HierarchyId.Read(BinaryReader reader)
         at Microsoft.EntityFrameworkCore.SqlServer.Storage.SqlServerHierarchyIdValueConverter.fromProvider(SqlBytes bytes)
         at lambda_method(Closure , QueryContext , DbDataReader , ResultContext , Int32[] , ResultCoordinator )
         at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.MoveNext()
System.InvalidCastException: No pattern found for: 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010100110010001001101111110000000
   at Microsoft.SqlServer.Types.SqlHierarchy.KnownPatterns.GetPatternByPrefix(BitReader bitR)
   at Microsoft.SqlServer.Types.SqlHierarchyId.Read(BinaryReader r)
   at Microsoft.EntityFrameworkCore.HierarchyId.Read(BinaryReader reader)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.SqlServerHierarchyIdValueConverter.fromProvider(SqlBytes bytes)
   at lambda_method(Closure , QueryContext , DbDataReader , ResultContext , Int32[] , ResultCoordinator )
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.Enumerator.MoveNext()

I've verified that I can read other tables (not FileTables) from this database.
Does this error indicate I'm doing something wrong?
Is there some incompatiblity with the way FileTables implement HierarchyIds?
Is it something else altogether?

Here's my entity class

	public partial class StoredFileTableDatum
	{
		public Guid stream_id { get; set; }
		public byte[] file_stream { get; set; }
		public string name { get; set; }
		public HierarchyId path_locator { get; set; }
		public HierarchyId parent_path_locator { get; set; }
		public string file_type { get; }
		public long cached_file_size { get; }
		public DateTimeOffset creation_time { get; set; }
		public DateTimeOffset last_write_time { get; set; }
		public DateTimeOffset last_access_time { get; set; }
		public bool is_directory { get; set; }
		public bool is_offline { get; set; }
		public bool is_hidden { get; set; }
		public bool is_readonly { get; set; }
		public bool is_archive { get; set; }
		public bool is_system { get; set; }
		public bool is_temporary { get; set; }

		public static string ConvertGuidToHierarchyId(Guid primaryKey)
		{
			var oBytes = new List<byte>(primaryKey.ToByteArray());
			oBytes.InsertRange(0, new byte[] { 0, 0 });
			oBytes.InsertRange(8, new byte[] { 0, 0 });
			oBytes.InsertRange(16, new byte[] { 0, 0, 0, 0 });

			var b1 = oBytes.Take(8).Reverse().ToArray();
			var b2 = oBytes.Skip(8).Take(8).Reverse().ToArray();
			var b3 = oBytes.Skip(16).Take(8).Reverse().ToArray();

			var sub1 = BitConverter.ToInt64(b1, 0);
			var sub2 = BitConverter.ToInt64(b2, 0);
			var sub3 = BitConverter.ToInt64(b3, 0);

			return string.Format(CultureInfo.InvariantCulture, "/{0}.{1}.{2}/", sub1, sub2, sub3);
		}
	}

and my entity configuration class

	public sealed class StoredFileTableDatumConfiguration : IEntityTypeConfiguration<StoredFileTableDatum>
	{
		public void Configure(EntityTypeBuilder<StoredFileTableDatum> entity)
		{
			entity.ToTable("StoredFileTableData", "5Binaries");

			entity.HasKey(e => e.path_locator).IsClustered(false);

			entity.Property(e => e.stream_id).IsRequired();
			entity.Property(e => e.name).HasMaxLength(255).IsRequired();
			entity.Property(e => e.path_locator).IsRequired();
			entity.Property(e => e.parent_path_locator).ValueGeneratedOnAddOrUpdate();
			entity.Property(e => e.cached_file_size).ValueGeneratedOnAddOrUpdate();
		}
	}

GetDescendant returns an invalid HierarchyId string exception

The following code raises an exception:

        var first = HierarchyId.Parse("/0/");
        var second = HierarchyId.Parse("/1/");
        var parent = first.GetAncestor(1);

        var newNode = parent.GetDescendant(first, second);

I've tested this in SQL and it returns the correct value of /0.1/ where the component appears to return //0.1/.

`using Microsoft.EntityFrameworkCore;` is generated twice when scaffolding with data annotations

See CSharpEntityTypeGeneratorTest.cs

                AssertFileContents(
                    @"using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace TestNamespace
{
    public partial class Patriarch
    {
        [Key]
        public HierarchyId Id { get; set; }
        public string Name { get; set; }
    }
}
",

GetAncestor crashes with ArgumentNullException

HierarchyId.GetAncestor's summary says:

<returns>
  A <see cref="HierarchyId"/> value represengint the <paramref name="n"/>th ancestor of this node or null if 
  <paramref name="n"/> is greater than <see cref="GetLevel"/>.
</returns>

But in reality if you do HierarchyId.Parse("/").GetAncestor(1) it crashes with ArugmentNullException in the ctor.

Support for EFCore 6.0

Hi,

I'm interested in using HierarchyId with EFCore/.NET 6 and was wondering if/when you plan to release version 3.0.0.

Best regards

Not support dapper

using Dapper;
using Microsoft.EntityFrameworkCore;

var sql = $" select * from tableA where columnA = '{columnA}'";
using (IDbConnection db = new SqlConnection(......))
{
    var result = await db.QueryAsync<EntityA>(sql);
    return result.ToList();
}

class EntityA
{
    public HierarchyId columnA {get;set;}
}

will get the error:

image

Contribute to dotnet/efcore

Now that an official version of Microsoft.SqlServer.Types is available, the EF Core team is willing to accept this code into the main EF Core repository. To do that, we'll need everyone that contributed to sign the .NET Foundation CLA.

Can I get acknowledgement from everyone that they are willing to (or already did) sign the CLA? After that, I'll prepare the PR.

Note, the code will continue to be licensed under the MIT--no changes there.

And thanks again for everyone's help getting us to this point!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.