Skip to content

NpgsqlTsVector HasGeneratedTsVectorColumn with Json Columns migration creation fails #3075

Description

@w7rus

Npgsql.EntityFrameworkCore.PostgreSQL 8.0.0

The documentation states that:

Starting with 7.0, the provider can also create computed tsvector columns over JSON columns. Simply use HasGeneratedTsVectorColumn() as shown above, and when applied to JSON columns, the provider will automatically generate json_to_tsvector/jsonb_to_tsvector as appropriate.

Note that this will pass the filter all to these functions, meaning that all values in the JSON document will be included. To customize the filter - or to create the computed column on older versions of the provider - simply specify the function yourself via HasComputedColumnSql.

OrderItem entity has a ProductSnapshot JSON column:

Entities:

public record OrderItem : EntityBase
{
    ...
    public ProductSnapshot ProductSnapshot { get; set; }
    public NpgsqlTsVector SearchVector { get; set; }
    ...
}

public record ProductSnapshot : SnapshotBase
{
    public string Alias { get; set; }
    public string Description { get; set; }
    public int Price { get; set; }
    public string Currency { get; set; }
    public Dictionary<string, string> Metadata { get; set; }
}

ModelBuilder:

modelBuilder.Entity<OrderItem>(_ =>
        {
           ...

            _.OwnsOne(__ => __.ProductSnapshot, ___ => { ___.ToJson(); });
            
            _.HasGeneratedTsVectorColumn(
                    __ => __.SearchVector,
                    "russian",
                    __ => __.ProductSnapshot
                )
                .HasIndex(__ => __.SearchVector)
                .HasMethod("GIN");

           ...
        });

This way, when migration is created dotnet ef raises following error:

System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.EntityFrameworkCore.Metadata.Internal.RelationalPropertyOverrides.Find(IReadOnlyProperty property, StoreObjectIdentifier& storeObject)
   at Microsoft.EntityFrameworkCore.RelationalPropertyExtensions.FindOverrides(IReadOnlyProperty property, StoreObjectIdentifier& storeObject)
   at Microsoft.EntityFrameworkCore.RelationalPropertyExtensions.GetColumnName(IReadOnlyProperty property, StoreObjectIdentifier& storeObject)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal.NpgsqlAnnotationProvider.<>c__DisplayClass2_1.<For>b__10(String p2)
   at System.Linq.Enumerable.SelectArrayIterator`2.Fill(ReadOnlySpan`1 source, Span`1 destination, Func`2 func)
   at System.Linq.Enumerable.SelectArrayIterator`2.ToArray()
   at Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal.NpgsqlAnnotationProvider.For(IColumn column, Boolean designTime)+MoveNext()
   at Microsoft.EntityFrameworkCore.Infrastructure.AnnotatableBase.AddAnnotations(AnnotatableBase annotatable, IEnumerable`1 annotations)
   at Microsoft.EntityFrameworkCore.Infrastructure.AnnotatableBase.AddAnnotations(IEnumerable`1 annotations)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.RelationalModel.Create(IModel model, IRelationalAnnotationProvider relationalAnnotationProvider, IRelationalTypeMappingSource relationalTypeMappingSource, Boolean designTime)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.RelationalModel.Add(IModel model, IRelationalAnnotationProvider relationalAnnotationProvider, IRelationalTypeMappingSource relationalTypeMappingSource, Boolean designTime)
   at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelRuntimeInitializer.InitializeModel(IModel model, Boolean designTime, Boolean prevalidation)
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelRuntimeInitializer.Initialize(IModel model, Boolean designTime, IDiagnosticsLogger`1 validationLogger)
   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_DesignTimeModel()
   at Microsoft.EntityFrameworkCore.Metadata.Internal.DesignTimeModel.get_Model()
   at Microsoft.EntityFrameworkCore.Design.DesignTimeServiceCollectionExtensions.<>c__DisplayClass1_0.<AddDbContextDesignTimeServices>b__10(IServiceProvider _)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, 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(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
Object reference not set to an instance of an object.

When explicitly doing via HasComputedColumnSql, there's no issue:

modelBuilder.Entity<OrderItem>(_ =>
        {
            ...

            _.OwnsOne(__ => __.ProductSnapshot, ___ => { ___.ToJson(); });
            
            _.Property(__ => __.SearchVector)
                .HasComputedColumnSql(
                    @$"jsonb_to_tsvector('russian', ""{nameof(OrderItem.ProductSnapshot)}""::jsonb, '""all""')",
                    stored: true);

            ...
        });

Activity

  1. roji commented on Oct 29, 2024

    @roji
    Member

    @w7rus sorry this didn't receive my attention earlier. Confirmed the bug, see minimal repro below.

    IIRC the docs refer to using a jsonb column that's mapped as a .NET string; owned entity JSON columns are represented a bit differently within EF Core, and that's likely the problem here.

    In any case, am putting this in the backlog - it's possible to work around this by simply defining the computed column SQL manually as you've done above.

    Repro
    await using var context = new BlogContext();
    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();
    
    public class BlogContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder
                .UseNpgsql("Host=localhost;Username=test;Password=test")
                .LogTo(Console.WriteLine, LogLevel.Information)
                .EnableSensitiveDataLogging();
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Blog>(e =>
            {
                e.OwnsOne(o => o.Owned).ToJson();
                e.HasGeneratedTsVectorColumn(
                        b => b.SearchVector,
                        "english",
                        b => b.Owned);
            });
        }
    }
    
    public class Blog
    {
        public int Id { get; set; }
        public Owned Owned { get; set; }
        public NpgsqlTsVector SearchVector { get; set; }
    }
    
    public class Owned
    {
        public string Something { get; set; }
    }
  2. added this to the Backlog milestone on Oct 29, 2024
  3. added theissue type on Jun 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions