efcore.pg icon indicating copy to clipboard operation
efcore.pg copied to clipboard

Support table splitting with HasGeneratedTsVectorColumn

Open span opened this issue 3 years ago • 3 comments

I am trying to add owned types to my NpgsqlTsVector column, but when adding the migration it fails with an argument null exception.

Stack trace:

System.ArgumentNullException: Value cannot be null. (Parameter 'property')
   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.ToArray()
   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
   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, Boolean designTime)
   at Microsoft.EntityFrameworkCore.Metadata.Internal.RelationalModel.Add(IModel model, IRelationalAnnotationProvider relationalAnnotationProvider, 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.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   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.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   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.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   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.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)
Value cannot be null. (Parameter 'property')

Config (firstname is string, adress is owned type):

        modelBuilder.Entity<CustomerDao>().OwnsOne(k => k.Address);
        modelBuilder.Entity<CustomerDao>()
            .HasGeneratedTsVectorColumn(
                p => p.SearchVector,
                "english",  // Text search config
                p => new { p.FirstName, p.Address})
            .HasIndex(p => p.SearchVector)
            .HasMethod("GIN"); //

I can't find any references or examples where owned types are used for the search column so perhaps this is not supported? Any way to work around it? Perhaps serialize into a json column?

span avatar Jun 03 '22 04:06 span

You cannot include an entity type directly in HasGeneratedTsVectorColumn - you must reference specific columns you want included.

However, unfortunately drilling into an owned entity is also unsupported at the moment - see https://github.com/dotnet/efcore/issues/11336 for a very similar issue for indexes with EF Core (once that's done, we can do the same thing here).

The good news is that it's easy to work around this: HasGeneratedTsVectorColumn is nothing but sugar over HasComputedColumnSql. So you can simply configure SearchVector with HasComputedColumn and directly provide the SQL to reference the columns you want. This typically would something like the following:

modelBuilder.Entity<Blog>().Property(b => b.TsVector)
    .HasComputedColumnSql(@"to_tsvector('english', ""FirstName"", ""Address_Something"")");

I'll leave this open to track adding support to HasGeneratedTsVectorColumn someday.

roji avatar Jun 03 '22 07:06 roji

Wow, very interesting. Thank you for your quick reply.

When using HasComputedColumnSql, there is no need/possibility to specify HasIndex and HasMethod?

span avatar Jun 03 '22 19:06 span

@span no, you still need to specify HasIndex if you want an index. HasComputedColumnSql only creates the computed column - it does not create an index for you.

roji avatar Jun 03 '22 21:06 roji