Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Annotations not preserved with adjustindexes [NPGSQL] #829

Closed
fbjerggaard opened this issue May 21, 2024 · 6 comments · Fixed by #832 or #883
Closed

Annotations not preserved with adjustindexes [NPGSQL] #829

fbjerggaard opened this issue May 21, 2024 · 6 comments · Fixed by #832 or #883

Comments

@fbjerggaard
Copy link
Contributor

Using Finbuckle.MultiTenant 6.13.1 - Haven't tried in 7.0.1 but a quick look at the source tells me there are no changes in this part of the source.

Have the following code:

NpgsqlIndexBuilderExtensions.IncludeProperties(
	builder.HasIndex(x => new { x.Street, x.CountryCode, x.ZipCode, x.Created }),
	x => new { x.City, x.Coordinates, x.ValidatedBy })
	.IsCreatedConcurrently();
builder.IsMultiTenant().AdjustIndexes();

This results in the following migration (when creating only this index):

migrationBuilder.CreateIndex(
	name: "ix_address_street_country_code_zip_code_created",
	table: "address",
	columns: new[] { "street", "country_code", "zip_code", "created" })

However, swapping those two lines in the configuration "fixes" it:

builder.IsMultiTenant().AdjustIndexes();

NpgsqlIndexBuilderExtensions.IncludeProperties(
	builder.HasIndex(x => new { x.Street, x.CountryCode, x.ZipCode, x.Created }),
	x => new { x.City, x.Coordinates, x.ValidatedBy })
	.IsCreatedConcurrently();

Results in the following migration:

migrationBuilder.CreateIndex(
	name: "ix_address_street_country_code_zip_code_created",
	table: "address",
	columns: new[] { "street", "country_code", "zip_code", "created" })
	.Annotation("Npgsql:CreatedConcurrently", true)
	.Annotation("Npgsql:IndexInclude", new[] { "city", "coordinates", "validated_by" });

The only part missing is the TenantID here, which is expected due to the order in the configuration.

I think it might be fixable by adding this to the AdjustIndex method in MultiTenantEntityTypeBuilder

if (index.GetAnnotation() is IIndexAnnotation annotation)
{
	indexBuilder.HasAnnotation(annotation);
}
@AndrewTriesToCode
Copy link
Contributor

I agree. Would you like to submit a PR or wait for me to make the change? Also in the first set of results you share above shouldn't tenant id be part of the generated migration?

@fbjerggaard
Copy link
Contributor Author

Also in the first set of results you share above shouldn't tenant id be part of the generated migration?

Yes it should - I manually edited the code when pasting into here and forgot about that.

I can take a look at implementing the fix on monday

fbjerggaard pushed a commit to fbjerggaard/Finbuckle.MultiTenant that referenced this issue May 26, 2024
fbjerggaard added a commit to fbjerggaard/Finbuckle.MultiTenant that referenced this issue May 26, 2024
fbjerggaard pushed a commit to fbjerggaard/Finbuckle.MultiTenant that referenced this issue Jun 8, 2024
fbjerggaard added a commit to fbjerggaard/Finbuckle.MultiTenant that referenced this issue Oct 8, 2024
@fbjerggaard
Copy link
Contributor Author

@AndrewTriesToCode I experienced a weird issue that I am pretty sure is related to this change after updating my Identity API to the latest 7.x - I get the following exception:

System.InvalidOperationException: The annotation 'Relational:Name' cannot be added because an annotation with the same name already exists on the object Key: IdentityUserLogin<string>.LoginProvider, IdentityUserLogin<string>.ProviderKey, IdentityUserLogin<string>.TenantId PK
   at Microsoft.EntityFrameworkCore.Infrastructure.AnnotatableBase.AddAnnotation(String name, Annotation annotation)
   at Microsoft.EntityFrameworkCore.Infrastructure.AnnotatableBase.AddAnnotation(String name, Object value)
   at Microsoft.EntityFrameworkCore.Infrastructure.AnnotatableBase.AddAnnotations(AnnotatableBase annotatable, IEnumerable`1 annotations)
   at Microsoft.EntityFrameworkCore.Infrastructure.AnnotatableBase.AddAnnotations(IEnumerable`1 annotations)
   at Finbuckle.MultiTenant.EntityFrameworkCore.MultiTenantEntityTypeBuilder.AdjustKey(IMutableKey key, ModelBuilder modelBuilder)
   at Finbuckle.MultiTenant.MultiTenantEntityTypeBuilderExtensions.AdjustKeys(MultiTenantEntityTypeBuilder builder, ModelBuilder modelBuilder)
   at Finbuckle.MultiTenant.EntityFrameworkCore.MultiTenantIdentityDbContext`3.OnModelCreating(ModelBuilder builder)
   at Finbuckle.MultiTenant.EntityFrameworkCore.MultiTenantIdentityDbContext`1.OnModelCreating(ModelBuilder builder)
   at Database.Identitys.IdentityContext.OnModelCreating(ModelBuilder builder) in D:\Code\Database\IdentityContext.cs:line 45
   at Microsoft.EntityFrameworkCore.Infrastructure.ModelCustomizer.Customize(ModelBuilder modelBuilder, DbContext context)
   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.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__8_4(IServiceProvider p)
   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.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.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(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.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.get_ChangeTracker()
   at Database.Identitys.IdentityContext..ctor(IMultiTenantContextAccessor multiTenantContextAccessor, DbContextOptions`1 options) in D:\Code\Database\IdentityContext.cs:line 24
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   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(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
   at Helpers.MigrateDatabase(WebApplication app) in D:\Code\Api\Helpers\MigrateDatabase.cs:line 23
   at Program.<Main>$(String[] args) in D:\Code\Api\Program.cs:line 91
   at Program.<Main>(String[] args)

However, I am having a hard time trying to write a unit test that makes it break since I can't figure out why the new key already has an annotation.

The fix is probably as simple as this from my fork: fbjerggaard@d78b566

It could also be made even more fail-safe so we never overwrite the annotation if it already exists:

foreach (var annotation in annotations)
{
    if (newKey?.FindAnnotation(annotation.Name) is null)
    {
        newKey?.AddAnnotation(annotation.Name, annotation.Value);
    }
}

Which method do you prefer?

@AndrewTriesToCode
Copy link
Contributor

Thanks for following up. Annotations are tough since their meaning is completely up to the data provider. I’ll take a closer look but I suspect your recommendation to simply not copy an existing annotation will do the trick.

@AndrewTriesToCode
Copy link
Contributor

I liked your approach in the comment above and went ahead with it. Thanks!

@AndrewTriesToCode
Copy link
Contributor

🎉 This issue has been resolved in version 8.0.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment