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)
-
0
You can try to upgrade the abp package to v5.1.0
https://github.com/aspnetboilerplate/aspnetboilerplate/issues/4992#issuecomment-550979369
-
0
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.
-
0
hi @tusksoft I will investigate this issue. : )
-
0
This issue is closed because it has not had recent activity for a long time.
-
0
Re-opening this, we are still having issues with TPH entity with an OwnedType
-
0
hi tusksoft
Have you tried upgrading abp to the latest version? v5.4
-
0
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.
-
0
Can we directly upgrade abp version without updating AspnetZero? we are currently on 8.1
-
0
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
-
0
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;
-
0
Hi @tusksoft
Yes,
Configuration.EntityHistory.IsEnabled
disables entity history and takes precedence. -
0
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?
-
0
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.