Open Closed

Design question: Using extra properties without static property name #2164


User avatar
0
LW created
  • ABP Framework version: 4.4.2
  • UI type: Angular
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): no

We have a situation where we should attatch some extra information to our entites. For example: EntityX has some properties and in addition Tenant1 needs to see extra property "Xyx" and it's value in UI. Tenant2 also needs extra property for EntityX but with different name "Abc" and possibly different value type. These extra properties cannot be determined at build time. At our old solution we just had extra columns for some database tables and meaning of the value was taken care of in customer guidance. I'm looking for more elegant solution than that and was wondering if the Extra Properties -model could provide the solution?


5 Answer(s)
  • User Avatar
    0
    berkansasmaz created
    Support Team .NET Developer

    By default, ABP saves all custom properties in the ExtraProperties field as a single JSON, object. If you prefer to create a tablespace for a custom property, you can configure it as follows in the EntityFrameworkCore project in the class MyProjectNameEfCoreEntityExtensionMappings.cs.

    ObjectExtensionManager.Instance
        .MapEfCoreProperty<IdentityUser, string>(
            "SocialSecurityNumber",
            (entityBuilder, propertyBuilder) =>
            {
                propertyBuilder.HasMaxLength(64).IsRequired().HasDefaultValue("");
            }
        );
    

    However when you do this, you cannot change the value type and name as the property will be kept as a separate column in the database table, so you should use the default.

    As mentioned in this document, you can save the data with the name and value type you want in the ExtraProperties column by using SetProperty.

  • User Avatar
    0
    LW created

    Hello, I think the JSON-field will suffice. But the problem isn't that can we map it to another table or not. The problem is that the meaning and value types change will change accross different tenants. For example: Tenant1 needs an additional property "SocialSecurityNumber" which is of type string but Tenant2 doen't care about that information. It needs an additional property "PersonLength" of type integer. We cannot know in advance what additional information each tenant needs, so the properties cannot be defined in advance like in the SetProperty-example. The JSON value would be created dynamically to the database by an external system (an integrator).

  • User Avatar
    0
    berkansasmaz created
    Support Team .NET Developer

    I understand the problem, I just gave the example code in my previous answer to emphasize that you should use ABP's default behavior, not the MapEfCoreProperty method.

    As you said, JSON-field will suffice. So just use the ExtraProperties field.

    When you save a few values in the ExtraProperties field and open the database, can you see that value in the ExtraProperties field? For example, can't you currently save SocialSecurityNumber as 123 for Tenant1 and PersonLength as 99 for Tenant2 in the ExtraProperties field in the database?

    If you can't save it, please share the sample code or I can share the sample code if you want 😊

  • User Avatar
    0
    LW created

    I have no doupt that saving isn't a problem with this. To be clear, I haven't tried any solution yet, hence the "Design Question". However all the examples I have seen define the extraproperty name staticly and I'm trying to figure out if this king dynamic definition would work as well. If you have some sample code, I will gladly take a look at it :)

  • User Avatar
    0
    berkansasmaz created
    Support Team .NET Developer

    As an example, I am sharing the code of the sample I wrote quickly.

    I created a folder named IdentityUsers under the MvcPro.Application project and into created the class named MyIdentityUserAppService as follows:

    [Dependency(ReplaceServices = true)]
    [ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(MyIdentityUserAppService))]
    public class MyIdentityUserAppService : IdentityUserAppService, IIdentityUserAppService
    {
        public MyIdentityUserAppService(
            IdentityUserManager userManager,
            IIdentityUserRepository userRepository,
            IIdentityRoleRepository roleRepository,
            IOrganizationUnitRepository organizationUnitRepository,
            IIdentityClaimTypeRepository identityClaimTypeRepository,
            IdentityProTwoFactorManager identityProTwoFactorManager,
            IOptions<IdentityOptions> identityOptions,
            IDistributedEventBus distributedEventBus) :
            base(
                userManager,
                userRepository,
                roleRepository,
                organizationUnitRepository,
                identityClaimTypeRepository,
                identityProTwoFactorManager,
                identityOptions,
                distributedEventBus)
        {
        }
    
        public override async Task<IdentityUserDto> CreateAsync(IdentityUserCreateDto input)
        {
            var identityUserDto = await base.CreateAsync(input);
    
            var user = await base.UserRepository.GetAsync(identityUserDto.Id);
            if (CurrentTenant.Id == null)
            {
                user.SetProperty("SocialSecurityNumber", "My SocialSecurityNumber is 123");
            }
            else
            {
                user.SetProperty("PersonLength", 99);
            }
    
            await base.UserRepository.UpdateAsync(user);
    
            return identityUserDto;
        }
    }
    

    As you can see from the code, I set two completely different properties that vary depending on the tenant if there is or not.

    While testing the code, logging in with the admin user, clicking Identity Management from the menu, then Users, creating a new user. Then create a new tenant and login with the tenant's admin user, then create a user again.

    In the picture below you can see that it is saved in the database.

    You can apply similar logic according to your needs.

    I'm closing this question because I think it's clear enough. If you run into a problem with this, feel free to open it.

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11