Not sure how to create a "simple" project that still reflects the setup and intent that we want to use within our Application. But since the project contains very little IP, I have no issue sharing the solution in its entirety.
Could this be transferred to you in a secure manner?
Hi bolenton,
(Note: Making some assumptions here about the version of ABP that you are using, so I am going to assume v4.4 or v5.0 for the sake of my answer)
Disclaimer: Though my answer may work for your particular case, please don't view this as an official response from Support nor the proper and/or intended work-around to address this particular issue.
Whenever you create a new Tenant, there is an event handler class that is fired inside your project/module: AcmeTenantDatabaseMigrationHandler
(where Acme is your project/module name).
Inside this class there is a method sequence that is being called whenever a new Tenant is created:
public async Task HandleEventAsync(TenantCreatedEto eventData)
This is called when you create a Tenant from inside the SaaS management screens. Inside it makes a call to the MigrateAndSeedForTenantAsync
method that does a few things:
There seems to be however a small issue, though perhaps unrelated, when [v] Use Shared Database is used, the code should not try to find an associated connection string for the newly created Tenant. When it cannot find the connection string, it tries to resolve an invalid database connection, which subsequently times out.
Though personally not sure how it fixes it, inside the MigrateAndSeedForTenantAsync
method you can see the usage of the Unit of Work that is responsible for commiting your Tenant admin user:
// Seed data
using (var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: true))
{
await _dataSeeder.SeedAsync(
new DataSeedContext(tenantId)
.WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName, adminEmail)
.WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, adminPassword)
);
await uow.CompleteAsync();
}
Here you can see that requiresNew: true
is being used, which if I understand it correctly, launches a new Unit of Work, which then cannot resolve the connection string of the current Tenant, and failes (due to time out). By settings this value to false, the "current" connection string of the existing Unit of Work is used, and thus the admin user is created as expected.
using (var uow = _unitOfWorkManager.Begin(requiresNew: false, isTransactional: true))
Hope this helps.
@albert,
The same thing happens for the <MyModule>ApplicationAutoMapperProfile
class. I am regenerating the code, since I made changes to the templates, and the mapping code is then duplicated.
Situation after first code generation:
CreateMap<OrganizationUnit, LookupDto<Guid>>().ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.DisplayName));
CreateMap<IdentityUser, LookupDto<Guid?>>().ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.Email));
Method contents after second code generation:
CreateMap<OrganizationUnit, LookupDto<Guid>>().ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.DisplayName));
CreateMap<IdentityUser, LookupDto<Guid?>>().ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.Email));
// This is duplicated
CreateMap<IdentityUser, LookupDto<Guid>>().ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.Email));
As you can observe, it seems that the CRUD generator does not "see" that this mapping already exists? Similarly, this also happens with other mapping statements, where you would end up with multiple definitions when you regenerate the code for multiple entities in succession (still well within regular usage of the CRUD tooling I would say):
CreateMap<Company, CompanyDto>();
CreateMap<CompanyWithNavigationProperties, CompanyWithNavigationPropertiesDto>();
CreateMap<Company, LookupDto<Guid?>>().ForMember(dest => dest.DisplayName, opt => opt.MapFrom(src => src.Name));
// This is duplicated
CreateMap<Company, CompanyDto>();
Edit: The namespace duplication happens a lot as well for the *AppService
classes and the classes *WithNavigationPropertiesDto
.
(though I do realize this is minor and will not actually produce error situations)
Hope this explains it well enough, there are other parts where the Regex seems to be too greedy and will match too much code, even code the CRUD generator did not create, but I moved that code out to separate files to avoid it all to gether.
Thanks again for your time and effort.
ABP: v5.0.0-rc.1 CLI: v5.0.0-rc.1 Suite: v5.0.0-rc.1
When (re)generating the the various files using the CRUD generator the <MyModule>EntityFrameworkCoreModule class will:
using Acme.Abp.Crm.Contacts;
using Acme.Abp.Crm.Companies;
using Acme.Abp.Crm.Companies;
using Acme.Abp.Crm.Companies;
using Acme.Abp.Crm.Contacts;
using Acme.Abp.Crm.Companies;
options.AddRepository<Company, Companies.EfCoreCompanyRepository>();
options.AddRepository<Contact, Contacts.EfCoreContactRepository>();
options.AddRepository<Company, Companies.EfCoreCompanyRepository>();
options.AddRepository<Company, Companies.EfCoreCompanyRepository>();
options.AddRepository<Company, Companies.EfCoreCompanyRepository>();
options.AddRepository<Contact, Contacts.EfCoreContactRepository>();
Edit: This will happen after an Entity is updated (more properties, extra navigations) and then Save and generate is clicked, or when you simply click it twice. When you then switch to another entity that gets updated or otherwise and click Save and generate then this one will be repeated as well.
Thank you @albert!
The entire ABP Suite cannot handle file scoped namespaces, so that is a bigger issue. With the move to .NET 6 for ABP v5 it would be essential to have this working, e.g. after refactoring your code Suite does not understand it at all.
As an added feedback on the enums, if the enum is not of type int, everything else fails, this also applies to flagged enums. I am not sure if ABP Suite CRUD generator supports flags enums but I had to refactor it to explicit int values to work.
In addition, the enum values are added to JSON as: Enum:EnumType:EnumValue
but if you look at https://docs.abp.io/en/abp/5.0/UI/AspNetCore/Tag-Helpers/Form-elements#label-localization-1 it says it looks in the localization texts with EnumType.EnumValue
so right now I have untranslated enums everywhere.
Note: It would be very helpful to have a separate v5 issues thread like exists for v4.4 Note: If these issues should be posted on Github instead let us know :)
Thanks alot for the quick reply and action, looking forward :)