Aspnetboilerplate: External Authentication does not work sometime when user does not exist

Created on 15 May 2018  ·  37Comments  ·  Source: aspnetboilerplate/aspnetboilerplate

I using ABP framework for my project. It is AspCore + Angular target Full .Net framework. I am using External Authentication for login. I have followed the steps mentioned in the document. I have configured it to use Windows Authentication which works fine when the user already exists in the DB. I noticed that when it is the first time that user is visiting the website it gently registers the user and then authenticates it into the system. For some reason sometimes when it is the first time that user is visiting the website it throws an error. After like 10-20 refresh it works fine sometimes. Interestingly when it finally goes in after several page refresh the user id jumps from the latest which (e.g. user id 6) to user id 10000 or from 10004 to 20000.

Here is the error in the log:

INFO 2018-05-15 11:19:54,995 [13 ] ore.Mvc.Internal.ControllerActionInvoker - Executing action method SAH.NEO.Controllers.TokenAuthController.Authenticate (SAH.NEO.Web.Core) with arguments (SAH.NEO.Models.TokenAuth.AuthenticateModel) - ModelState is Valid ERROR 2018-05-15 11:19:55,042 [9 ] Mvc.ExceptionHandling.AbpExceptionFilter - There is no such an entity. Entity type: SAH.NEO.Authorization.Users.User, id: 9 Abp.Domain.Entities.EntityNotFoundException: There is no such an entity. Entity type: SAH.NEO.Authorization.Users.User, id: 9 at Abp.Domain.Repositories.AbpRepositoryBase2.d__21.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Abp.Threading.InternalAsyncHelper.d__51.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Abp.Authorization.Users.AbpUserStore2.d__88.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Abp.Authorization.Users.AbpUserManager2.d__47.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Abp.Authorization.AbpLogInManager3.d__38.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at Abp.Authorization.AbpLogInManager3.d__37.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Abp.Authorization.AbpLogInManager3.d__36.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Abp.Threading.InternalAsyncHelper.d__51.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Nito.AsyncEx.Synchronous.TaskExtensions.WaitAndUnwrapExceptionTResult
at Nito.AsyncEx.AsyncContext.<>c__DisplayClass16_01.b__0(Task1 t)
at System.Threading.Tasks.ContinuationResultTaskFromResultTask2.InnerInvoke() at System.Threading.Tasks.Task.Execute() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Nito.AsyncEx.Synchronous.TaskExtensions.WaitAndUnwrapExceptionTResult
at Nito.AsyncEx.AsyncContext.RunTResult at Abp.Threading.AsyncHelper.RunSyncTResult
at Abp.Authorization.AbpLogInManagerExtensions.LoginTTenant,TRole,TUser at SAH.NEO.Controllers.TokenAuthController.GetLoginResult(String usernameOrEmailAddress, String password, String tenancyName) in C:\Projects\NEO\Main\SAH.NEO\src\SAH.NEO.Web.Core\Controllers\TokenAuthController.cs:line 197 at SAH.NEO.Controllers.TokenAuthController.d__8.MoveNext() in C:\Projects\NEO\Main\SAH.NEO\src\SAH.NEO.Web.Core\Controllers\TokenAuthController.cs:line 57 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at lambda_method(Closure , Object ) at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult() at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__12.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__10.MoveNext() --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__14.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__23.MoveNext() INFO 2018-05-15 11:19:55,042 [9 ] etCore.Mvc.Internal.ObjectResultExecutor - Executing ObjectResult, writing value Microsoft.AspNetCore.Mvc.ControllerContext. INFO 2018-05-15 11:19:55,042 [9 ] ore.Mvc.Internal.ControllerActionInvoker - Executed action SAH.NEO.Controllers.TokenAuthController.Authenticate (SAH.NEO.Web.Core) in 52.0181ms ERROR 2018-05-15 11:19:55,042 [9 ] Microsoft.AspNetCore.Server.Kestrel - Connection id "0HLDQ0BD8TOQA", Request id "0HLDQ0BD8TOQA:00000003": An unhandled exception was thrown by the application. System.InvalidOperationException: StatusCode cannot be set because the response has already started. at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Frame.set_StatusCode(Int32 value) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Frame.Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.set_StatusCode(Int32 value) at Microsoft.AspNetCore.Http.Internal.DefaultHttpResponse.set_StatusCode(Int32 value) at Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder.<>c.b__16_0(HttpContext context) at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.StaticFiles.DefaultFilesMiddleware.Invoke(HttpContext context) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.d__6.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Builder.RouterMiddleware.d__4.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.Owin.Mapping.MapMiddleware.d__0.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Owin.WebSocketAcceptAdapter.<>c__DisplayClass6_0.d.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.d__4.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.d__6.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at SAH.NEO.Web.Host.Startup.Startup.<>c.d.MoveNext() in C:\Projects\NEO\Main\SAH.NEO\src\SAH.NEO.Web.Host\Startup\Startup.cs:line 124 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.d__7.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Abp.AspNetCore.Security.AbpSecurityHeadersMiddleware.d__2.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware.d__11.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.d__3.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)

I have changed the GetLoginResultAsync to sync method as I thought it might be this that sometime works sometime does not but it was not it.

problem

Most helpful comment

I have taken a deeper look at the issue. This seems to be a problem when using UnitOfWork.IsTransactional with PostgreSQL

When a new user is created via AbpLoginManager.TryLoginFromExternalAuthenticationSources().
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/cf87dd654bab699dbb5962003efd55ac945e3e2c/src/Abp.ZeroCore/Authorization/AbpLoginManager.cs#L307-L323

The user entity is being tracked in Microsoft.EntityFrameworkCore.ChangeTracker.Entries() after SaveChangesAsync() is called followed by await UserManager.CreateAsync(user);.

However, the user is then updated in CreateLoginResultAsync() after the entity creation while it is still under the same transaction.
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/cf87dd654bab699dbb5962003efd55ac945e3e2c/src/Abp.ZeroCore/Authorization/AbpLoginManager.cs#L170-L201

https://github.com/aspnetboilerplate/aspnetboilerplate/blob/cf87dd654bab699dbb5962003efd55ac945e3e2c/src/Abp.ZeroCore/Authorization/AbpLoginManager.cs#L205-L234

The exception is thrown when UserManager.UpdateAsync(user) checks for old username from database.
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/cf87dd654bab699dbb5962003efd55ac945e3e2c/src/Abp.ZeroCore/Authorization/Users/AbpUserManager.cs#L613

And a new scope is used to retrieve old username
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/cf87dd654bab699dbb5962003efd55ac945e3e2c/src/Abp.ZeroCore/Authorization/Users/AbpUserStore.cs#L1221-L1232

By default, EfCoreUnitOfWork uses IsolationLevel.ReadUncommitted but it is treated as IsolationLevel.ReadCommitted in PostgreSQL

PostgreSQL's Read Uncommitted mode behaves like Read Committed.

https://github.com/aspnetboilerplate/aspnetboilerplate/blob/cf87dd654bab699dbb5962003efd55ac945e3e2c/src/Abp.EntityFrameworkCore/EntityFrameworkCore/Uow/DbContextEfCoreTransactionStrategy.cs#L38

However, this is the behaviour that currently not supported by PostgreSQL as stated below.

Read Committed is the default isolation level in PostgreSQL. When a transaction uses this isolation level, a SELECT query (without a FOR UPDATE/SHARE clause) sees only data committed before the query began; it never sees either uncommitted data or changes committed during query execution by concurrent transactions.

https://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-READ-COMMITTED

I would suggest to disable IsTransactional for UnitOfWork if you are using PostgreSQL.

c# public override void PreInitialize() { // more code .. Configuration.UnitOfWork.IsTransactional = false; // more code .. }

All 37 comments

@mohammad-shadmehr;

  1. What is your ABP version ?
  2. If it is the latest one, could you log the value of AbpSession.TenantId to see if it is the correct tenant (or host) that you want ?

my version is 3.5.0
I noticed that by adding [UnitOfWork(isTransactional: false)] to the Authenticate method it works fine though! Not sure if its relavant! Yes the AbpSession.TenantId has got a value

@mohammad-shadmehr could you try with ABP 3.6.1 to see if it fixes the problem ?

@ismcagdas That's my work project and I am on leave for a while. I will check and keep you posted but as I mentioned the problem was resolved once I set the [UnitOfWork(isTransactional: false)] attribute on the Authenticate method. Seems registration and login cannot be done in one transaction and registration needs to be committed prior the login

I had the same problem, external authentication would end up with

Mvc.ExceptionHandling.AbpExceptionFilter - There is no such an entity. Entity type: Bilbo.Authorization.Users.User, id: Abp.Domain.Entities.EntityNotFoundException: There is no such an entity. Entity type: Bilbo.Authorization.Users.User, id:

Then funny thing is that this fails only on my production environment, while on local machine everything works fine.

Then I put attribute [UnitOfWork(isTransactional: false)] on the ExternalAuthenticate method

[HttpPost]
[UnitOfWork(isTransactional: false)]  
public async Task<ExternalAuthenticateResultModel> ExternalAuthenticate([FromBody] ExternalAuthenticateModel model)

Everything works fine. I use Abp 3.5.0. I am going to upgrade to 3.6.2 soon, so I can report if fixes the problem

Hi,

I think I have the same problem, I just took the latest download package with 3.7.2 and only added an external source for Authentication :

public class MyAuthenticationSource : DefaultExternalAuthenticationSource<Tenant, User>, ITransientDependency
    {
        public override string Name
        {
            get { return "Mine"; }
        }

        public async override Task<bool> TryAuthenticateAsync(string userNameOrEmailAddress, string plainPassword, Tenant tenant)
        {
            if (tenant == null ||
                !userNameOrEmailAddress.Contains("@"))
                return false;
            return true;
        }
    }

this Authentication is correctly enabled (checked it with the debugger). And I did not make a single other modification to the package downloaded.

Here is how to reproduce it :

  • Connect as admin / default password on the host with swagger interface (my custom authentication will return false but that's ok)
  • Create a tenant with swagger (you also have to create the database) and for the email address use : [email protected]
  • Logout
  • Login to your newly created tenant with [email protected] and any password -> MyAuthenticationSource will always return true -> you'll get the error
  • By curiosity I tried to log with the admin email address ([email protected]) -> Authentication work perfectly fine and the content of AbpUser was updated

@ismcagdas please tell me if that's enough information for you or not ?

The full error I got is : {"result":null,"targetUrl":null,"success":false,"error":{"code":0,"message":"There is no entity User with id = 5!","details":null,"validationErrors":null},"unAuthorizedRequest":false,"__abp":true}

I do not know how to enable the log for now so I can't tell you more.

@seblucas Thank you for your detailed explanation. I couldn't understand on which step you get the error "There is no such an entity" ?

@ismcagdas
Let's hope you'll find the problem ;)

I finally found the log :

ERROR 2018-07-24 16:49:16,357 [3    ] Mvc.ExceptionHandling.AbpExceptionFilter - There is no such an entity. Entity type: QBoilerPlate.Authorization.Users.User, id: 8
Abp.Domain.Entities.EntityNotFoundException: There is no such an entity. Entity type: QBoilerPlate.Authorization.Users.User, id: 8
   at Abp.Domain.Repositories.AbpRepositoryBase`2.GetAsync(TPrimaryKey id) in D:\Github\aspnetboilerplate\src\Abp\Domain\Repositories\AbpRepositoryBase.cs:line 86
   at Abp.Threading.InternalAsyncHelper.AwaitTaskWithPostActionAndFinallyAndGetResult[T](Task`1 actualReturnValue, Func`1 postAction, Action`1 finalAction)
   at Abp.Authorization.Users.AbpUserStore`2.GetUserNameFromDatabaseAsync(Int64 userId)
   at Abp.Authorization.Users.AbpUserManager`2.UpdateAsync(TUser user) in D:\Github\aspnetboilerplate\src\Abp.ZeroCore\Authorization\Users\AbpUserManager.cs:line 362
   at Abp.Authorization.AbpLogInManager`3.CreateLoginResultAsync(TUser user, TTenant tenant) in D:\Github\aspnetboilerplate\src\Abp.ZeroCore\Authorization\AbpLoginManager.cs:line 224
   at Abp.Authorization.AbpLogInManager`3.LoginAsyncInternal(String userNameOrEmailAddress, String plainPassword, String tenancyName, Boolean shouldLockout)
   at Abp.Authorization.AbpLogInManager`3.LoginAsync(String userNameOrEmailAddress, String plainPassword, String tenancyName, Boolean shouldLockout)
   at Abp.Threading.InternalAsyncHelper.AwaitTaskWithPostActionAndFinallyAndGetResult[T](Task`1 actualReturnValue, Func`1 postAction, Action`1 finalAction)
   at QBoilerPlate.Controllers.TokenAuthController.GetLoginResultAsync(String usernameOrEmailAddress, String password, String tenancyName) in C:\Projets\QBoilerPlate\3.9.0\aspnet-core\src\QBoilerPlate.Web.Core\Controllers\TokenAuthController.cs:line 185
   at QBoilerPlate.Controllers.TokenAuthController.Authenticate(AuthenticateModel model) in C:\Projets\QBoilerPlate\3.9.0\aspnet-core\src\QBoilerPlate.Web.Core\Controllers\TokenAuthController.cs:line 55
   at lambda_method(Closure , Object )
   at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()

@ismcagdas
I've been reading the changelog and it seems there's nothing related to this problem, isn't it ? So no need to make a test with 3.8.0 ?

@seblucas didn't have a chance to try this.

No problem, I was just making sure ;-)

@seblucas I have tried the scenario in https://github.com/aspnetboilerplate/aspnetboilerplate/issues/3369#issuecomment-407442694 and it worked for me.

Do you have a project which we can reproduce this problem ?

Moving to v4.0 for now.

Sorry I missed your message, I'll build a new project and attach it to this issue later this evening.

First sorry for the delay, I got swamped at work friday :(.

I just tried with a brand new project downloaded from your site and I still have the same error

I'll attach all the code in a few minutes

image

And here is the sample project. I used git to show exactly what was added to your sample project. I used exactly the same process as my previous comment (https://github.com/aspnetboilerplate/aspnetboilerplate/issues/3369#issuecomment-407442694) and got the same error.

The tenant I added is "tenant1"

TestExternalAuth.zip

Also I use Posgres in case that help.

@seblucas following your steps and i made the following changes. However, i didn't manage to re-produce the issue. I have yet to try it with postgres (only tried with mssql for now). Can you try the following changes with postgresql on your end?

public override Task<bool> TryAuthenticateAsync(string userNameOrEmailAddress, string plainPassword, Tenant tenant)
{
    if (tenant == null ||
        !userNameOrEmailAddress.Contains("@"))
    {
        return Task.FromResult(false);
    }
    return Task.FromResult(true);
}

@ryancyq
I tried with your changes and it still produce exactly the same error

I also encountered this problem. Now it seems that we need to wait until the v4.0 version is possible to solve it?

Great at least that mean, I'm not the only one and I did not make a stupid error while testing external auth

@seblucas add [UnitOfWork(isTransactional: false)] to the Authenticate method as of a workaround and it should work!

Hi,

I have the same problem with LDAP (Abp.Domain.Entities.EntityNotFoundException: There is no such an entity).
Setting Login method to nontransactional works.
Abp version 3.9.0.

my code is work fine when i switch db provider to mysql or sqlserver, but not postgres. is there any defferent ?

@rrEmiZ do you use PostgreSQL as well ?

@mohammad-shadmehr
IIRC, I already did earlier when I first encountered this problem (in July) and it did not help but I'll test again tomorrow.

@humanww
The only difference I can see are the auto generated column ... at least it may explain why I feel so alone with this problem. Choosing Postgres was a challenging choice :(.

@rrEmiZ
I'll also try that tomorrow and report here. Thanks.

@rrEmiZ do you use PostgreSQL as well ?

Yes I use PostgreSQL.

I think the problem is that EF in pgsql reverts transaction with created entity when user entity is loaded from db (id is assigned, but entity is not saved), so it throws Entity not found and full transaction is reverted. For example if we assign IsActive flag to false, login method throws User is not active with assigned user name (id is sequence is enlarged, create method in userManager returns Success, but there is no such entity in db).

I have taken a deeper look at the issue. This seems to be a problem when using UnitOfWork.IsTransactional with PostgreSQL

When a new user is created via AbpLoginManager.TryLoginFromExternalAuthenticationSources().
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/cf87dd654bab699dbb5962003efd55ac945e3e2c/src/Abp.ZeroCore/Authorization/AbpLoginManager.cs#L307-L323

The user entity is being tracked in Microsoft.EntityFrameworkCore.ChangeTracker.Entries() after SaveChangesAsync() is called followed by await UserManager.CreateAsync(user);.

However, the user is then updated in CreateLoginResultAsync() after the entity creation while it is still under the same transaction.
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/cf87dd654bab699dbb5962003efd55ac945e3e2c/src/Abp.ZeroCore/Authorization/AbpLoginManager.cs#L170-L201

https://github.com/aspnetboilerplate/aspnetboilerplate/blob/cf87dd654bab699dbb5962003efd55ac945e3e2c/src/Abp.ZeroCore/Authorization/AbpLoginManager.cs#L205-L234

The exception is thrown when UserManager.UpdateAsync(user) checks for old username from database.
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/cf87dd654bab699dbb5962003efd55ac945e3e2c/src/Abp.ZeroCore/Authorization/Users/AbpUserManager.cs#L613

And a new scope is used to retrieve old username
https://github.com/aspnetboilerplate/aspnetboilerplate/blob/cf87dd654bab699dbb5962003efd55ac945e3e2c/src/Abp.ZeroCore/Authorization/Users/AbpUserStore.cs#L1221-L1232

By default, EfCoreUnitOfWork uses IsolationLevel.ReadUncommitted but it is treated as IsolationLevel.ReadCommitted in PostgreSQL

PostgreSQL's Read Uncommitted mode behaves like Read Committed.

https://github.com/aspnetboilerplate/aspnetboilerplate/blob/cf87dd654bab699dbb5962003efd55ac945e3e2c/src/Abp.EntityFrameworkCore/EntityFrameworkCore/Uow/DbContextEfCoreTransactionStrategy.cs#L38

However, this is the behaviour that currently not supported by PostgreSQL as stated below.

Read Committed is the default isolation level in PostgreSQL. When a transaction uses this isolation level, a SELECT query (without a FOR UPDATE/SHARE clause) sees only data committed before the query began; it never sees either uncommitted data or changes committed during query execution by concurrent transactions.

https://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-READ-COMMITTED

I would suggest to disable IsTransactional for UnitOfWork if you are using PostgreSQL.

c# public override void PreInitialize() { // more code .. Configuration.UnitOfWork.IsTransactional = false; // more code .. }

@ryancyq thanks, you are amazing 😄.
We can add this information to https://aspnetboilerplate.com/Pages/Documents/EF-Core-PostgreSql-Integration document then.

I would suggest to disable IsTransactional for UnitOfWork if you are using PostgreSQL.

public override void PreInitialize() {
    // more code ..
    Configuration.UnitOfWork.IsTransactional = false;
    // more code ..
}

Or disable Transactional only when necessary, when a new user is created and postresql is used..

@mdissel How would you do that ? You'll have to change boilerplate's code to add [UnitOfWork(isTransactional: false)] on top of the authentication function.

Thanks in advance.

Replying to myself, I just need to do that in TokenAuthController.cs :

        [UnitOfWork(isTransactional: false)]
        public async Task<AuthenticateResultModel> Authenticate([FromBody] AuthenticateModel model)

Correct!

I had the problem,i use latest version ABP
No mapping to a relational type can be found for property 'Abp.Webhooks.WebhookSubscriptionInfo.IsActive' with the CLR type 'bool'.

hi @ridvanozbugru Please create a new issue and explain your issue in detail.

  • Your Abp package version.
  • Your base framework: .Net Framework or .Net Core.
  • Exception message and stack trace if available.
  • Steps needed to reproduce the problem.
Was this page helpful?
0 / 5 - 0 ratings

Related issues

hikalkan picture hikalkan  ·  3Comments

iyhammad picture iyhammad  ·  3Comments

RiaanvE picture RiaanvE  ·  3Comments

AlexGeller picture AlexGeller  ·  3Comments

apchenjun picture apchenjun  ·  3Comments