JudahGabriel / RavenDB.Identity

RavenDB Identity provider for ASP.NET Core. Let RavenDB manage your users and logins.
https://www.nuget.org/packages/RavenDB.Identity/1.0.0
MIT License
61 stars 29 forks source link

Calling userManager.FindByEmailAsync right after userManager.CreateAsync return null but on breakpoint its fine #38

Closed farhadnowzari closed 3 years ago

farhadnowzari commented 3 years ago

Hi, So the plan is to register a user and then put the AccountId into the Person document and save the person document with that AccountId and the reason here is that the person can have different accounts and I cannot put them under the Person document because first it gets too big for no reason and second, the accounts are not independent. So I'm creating the account like,

await _userManager.CreateAsync(request.GetAccount(), request.Password);

then after the creation successful check I want to read the account with

await _userManager.FindByEmailAsync(request.EmailAddress)

to get the account and eventually get the Id of the account to put under the person table.

The problem is that after waiting on the creation and getting success the creation is not actually finished on the database so it causes me to receive null on finding the user. it works if on finding the user I set a breakpoint then step forward and then my account is there which means it is waited enough for the database to actually put the record in the database.

farhadnowzari commented 3 years ago

I will change the model now to have the account and the person as a document. Since I'm changing not to have multiple account I do not care if the data is not in database yet I will just send success or fail to the frontend.

farhadnowzari commented 3 years ago

Ok, so now the scenario is that I have create an account but I have to also put a picture there. But when I do userManager.CreateAsync(account, password) I do not have the id of the account to store an attachment for this document. right now I am doing a workaround and hardcoding the Id since I know it is like account/username but still it doesn't seem right. After that I await userManager.CreateAsync I expect that the data is in the database immediately or at least set the Id in advance and save later.

JudahGabriel commented 3 years ago

As documented in step 4, you'll need to call .SaveChangesAsync before your data will show up in the database.

If you need the user object, well, you already have it:

var user = request.GetAccount();

// Create it in the database:
var creationResult = await _userManager.CreateAsync(user, request.Password);
await dbSession.SaveChangesAsync(); // Save changes.

// Need to know the ID? Just check user.Id. This value will be available after saving changes.
var userId = user.Id; 
farhadnowzari commented 3 years ago

Ok that sounds good. But the issue is that even if I don't call save changes and I just do thread.sleep for one second right after UserManager.CreateAsync I get my user and I can query it which means it does not care about save changes, does it. I am creating my user inside a mediator command. And I want to handle this change by myself so I can cancel the transaction if something goes wrong

And If you notice here, https://github.com/JudahGabriel/RavenDB.Identity/blob/master/RavenDB.Identity/UserStore.cs In CreateAsync of UserStore, SaveChanges is already called on DbSession and it seems it takes a while until Ravendb creates it and that's why I cannot immediately get it.

JudahGabriel commented 3 years ago

CreateAsync internally saves changes because it is a cluster-wide operation that works outside the normal transaction scope.

Still, as a general rule, always call SaveChangeAsync once you're ready to commit your transaction.

it seems it takes a while until Ravendb creates it and that's why I cannot immediately get it.

This depends on what you're trying to do. Raven indexes don't update instantly -- they're eventually consistent. If you want to query an index instantly after changing data in the index, you need to configure SaveChangesAsync to wait for indexes:

session.Advanced.WaitForIndexesAfterSaveChanges();

If you do that, calling .SaveChangesAsync() will wait for stale indexes to become fresh with the updated data. Any successive queries will have the updated data.

farhadnowzari commented 3 years ago

Thanks for this information 👍. About the transaction though. I would expect if I need to close my transaction by calling the SaveChanges at the end of my method, any exception that happens between the CreateAsync and the SaveChannges must revert the user creation but it doesn't. I think it can be because I'm getting the document session registered through dependency injection and I'm not opening it inside one method? But still when SaveChannges happens in CreateAsync it is already actually saved the data and it won't revert it? Maybe this is something not related to this library(Ravendb.Identity)

JudahGabriel commented 3 years ago

any exception that happens between the CreateAsync and the SaveChannges must revert the user creation but it doesn't.

That's right. Normally, a Raven transaction would handle that. However, in order to enforce cluster-wide unique email addresses, we use Raven's compare/exchange functionality to ensure email uniqueness. Raven's compare/exchange is cluster wide, and operates outside of the transaction scope. Thus, after a successful email reservation, we call .SaveChangesAsync internally because the email reservation has been made in the cluster. If any exception happens, we rollback the email reservation.

You're right that any actions that take place after succesful user creation could fail, rollback your transaction, but the user would still be in the database. If this scenario is important to you, you'd need to write some special rollback code:

await userManager.CreateAsync(user);
try
{
   // do other stuff
}
catch (Exception error)
{
    // Don't save changes, and delete the user.
    await userManager.DeleteAsync(user);
}

Hope this helps.

farhadnowzari commented 3 years ago

Yup, Thanks for your answer