Base solution for your next web application
Open Closed

Null Reference Exception When Trying To Create User-Derived Entity #8221


User avatar
0
tusksoft created

We have created a domain entity StaffMember that inherits from User. Creating/editing staff members was working, but when adding an owned type, Address, onto the entity, we get a null reference exception when trying to create a new staff member with an address.

Here is the exception and stack trace, from the logs:

ERROR 2019-12-19 16:47:59,889 [33   ] Mvc.ExceptionHandling.AbpExceptionFilter - Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object.
   at Abp.EntityHistory.EntityHistoryHelper.CreateEntityChangeSet(ICollection`1 entityEntries)
   at Abp.Zero.EntityFrameworkCore.AbpZeroCommonDbContext`3.SaveChangesAsync(CancellationToken cancellationToken)
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesInDbContextAsync(DbContext dbContext)
   at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesAsync()
   at Abp.Authorization.Users.AbpUserStore`2.CreateAsync(TUser user, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Identity.UserManager`1.CreateAsync(TUser user)
   at Abp.Authorization.Users.AbpUserManager`2.CreateAsync(TUser user)
   at MyApp.Authorization.Users.StaffMemberAppService.CreateStaffMemberAsync(CreateOrUpdateStaffMemberInput input) in C:\Users\shodg\myapp\src\MyApp.Application\Domain\Core\StaffMembers\StaffMemberAppService.cs:line 290
   at Abp.Threading.InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func`1 postAction, Action`1 finalAction)
   at MyApp.Authorization.Users.StaffMemberAppService.CreateOrUpdateStaffMember(CreateOrUpdateStaffMemberInput input) in C:\Users\shodg\myapp\src\MyApp.Application\Domain\Core\StaffMembers\StaffMemberAppService.cs:line 203
   at Abp.Threading.InternalAsyncHelper.AwaitTaskWithPostActionAndFinally(Task actualReturnValue, Func`1 postAction, Action`1 finalAction)
   at lambda_method(Closure , Object )
   at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

And here is the method in our service that creates staff members:

        [AbpAuthorize(AppPermissions.Pages_Administration_StaffMembers_Create)]
        protected virtual async System.Threading.Tasks.Task CreateStaffMemberAsync(CreateOrUpdateStaffMemberInput input)
        {
            if (AbpSession.TenantId.HasValue)
            {
                await _userPolicy.CheckMaxUserCountAsync(AbpSession.GetTenantId());
            }

            var staffMember = ObjectMapper.Map<StaffMember>(input.User); //Passwords is not mapped (see mapping configuration)
            staffMember.TenantId = AbpSession.TenantId;

            //Set password
            if (input.SetRandomPassword)
            {
                var randomPassword = await _userManager.CreateRandomPassword();
                staffMember.Password = _passwordHasher.HashPassword(staffMember, randomPassword);
                input.User.Password = randomPassword;
            }
            else if (!input.User.Password.IsNullOrEmpty())
            {
                await UserManager.InitializeOptionsAsync(AbpSession.TenantId);
                foreach (var validator in _passwordValidators)
                {
                    CheckErrors(await validator.ValidateAsync(UserManager, staffMember, input.User.Password));
                }

                staffMember.Password = _passwordHasher.HashPassword(staffMember, input.User.Password);
            }

            staffMember.ShouldChangePasswordOnNextLogin = input.User.ShouldChangePasswordOnNextLogin;

            //TODO: replace US's countryID (220) with functionality to load states dynamically based on country selection
            await staffMember.Address.SetCountryAndState(_countryRepository, _stateRepository, 220, input.User.Address.StateId);

            //Assign roles
            staffMember.Roles = new Collection<UserRole>();
            foreach (var roleName in input.AssignedRoleNames)
            {
                var role = await _roleManager.GetRoleByNameAsync(roleName);
                staffMember.Roles.Add(new UserRole(AbpSession.TenantId, staffMember.Id, role.Id));
            }

            CheckErrors(await UserManager.CreateAsync(staffMember));
            await CurrentUnitOfWork.SaveChangesAsync(); //To get new user's Id.

            //Notifications
            await _notificationSubscriptionManager.SubscribeToAllAvailableNotificationsAsync(staffMember.ToUserIdentifier());
            await _appNotifier.WelcomeToTheApplicationAsync(staffMember);

            //Organization Units
            await UserManager.SetOrganizationUnitsAsync(staffMember, input.OrganizationUnits.ToArray());

            //Send activation email
            if (input.SendActivationEmail)
            {
                staffMember.SetNewEmailConfirmationCode();
                await _userEmailer.SendEmailActivationLinkAsync(
                    staffMember,
                    AppUrlService.CreateEmailActivationUrlFormat(AbpSession.TenantId),
                    input.User.Password
                );
            }
        }

The exception is thrown at CheckErrors(await UserManager.CreateAsync(staffMember)).

If helpful, here is the modelBuilder mapping for StaffMember class from our Db context:

            modelBuilder.Entity<StaffMember>(sm =>
            {
                sm.HasIndex(e => new { e.TenantId });
                sm.HasMany(typeof(Project), nameof(StaffMember.Projects));
                sm.OwnsOne(e => e.Address);
            });

Is there a workaround/solution to this behavior? Let us know if you need additional info, and we can provide.


13 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team

    You can try to upgrade the abp package to v5.1.0

    https://github.com/aspnetboilerplate/aspnetboilerplate/issues/4992#issuecomment-550979369

  • User Avatar
    0
    tusksoft created

    Thanks for your response. Unfortunately, upgrading to 5.1 did not fix the issue.

    Following the Github issue you linked, we have disabled entity history, and that works as a temporary workaround. But please let us know if/when there is a more stable solution.

  • User Avatar
    0
    maliming created
    Support Team

    hi @tusksoft I will investigate this issue. : )

  • User Avatar
    0
    ismcagdas created
    Support Team

    This issue is closed because it has not had recent activity for a long time.

  • User Avatar
    0
    tusksoft created

    Re-opening this, we are still having issues with TPH entity with an OwnedType

  • User Avatar
    0
    maliming created
    Support Team

    hi tusksoft

    Have you tried upgrading abp to the latest version? v5.4

  • User Avatar
    0
    tusksoft created

    We have not, but this was supposed to be supported in 5.1 too, and not really sure if anything has changed for EntityHistory between those versions.

  • User Avatar
    0
    tusksoft created

    Can we directly upgrade abp version without updating AspnetZero? we are currently on 8.1

  • User Avatar
    0
    maliming created
    Support Team

    Can we directly upgrade abp version without updating AspnetZero? we are currently on 8.1

    https://support.aspnetzero.com/QA/Questions/8730#answer-4ec81d18-1a55-65b0-77c3-39f426ffd35c

  • User Avatar
    0
    tusksoft created

    can you let me know the best practises around enabling/disabling EntityHistory Apparently you can toggle this on every project. Does any take precedence over the other?

    Configuration.EntityHistory.IsEnabled = false;

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @tusksoft

    Yes, Configuration.EntityHistory.IsEnabled disables entity history and takes precedence.

  • User Avatar
    0
    tusksoft created

    So how does it work? same entities created through a project with History disabled will not store history, but if those entities are added/modified via another project it will store. Or Entites defined in a project with History disabled will not store and History anywhere in the solution?

  • User Avatar
    0
    ismcagdas created
    Support Team

    same entities created through a project with History disabled will not store history, but if those entities are added/modified via another project it will store.

    Yes, that's correct if you have different configuration for different projects.