@@ -161,7 +161,7 @@ public override Task<IdentityResult> CreateAsync(
161161 }
162162
163163 /// <inheritdoc />
164- public override Task < IdentityResult > UpdateAsync (
164+ public override async Task < IdentityResult > UpdateAsync (
165165 MemberIdentityUser user ,
166166 CancellationToken cancellationToken = default )
167167 {
@@ -189,9 +189,21 @@ public override Task<IdentityResult> UpdateAsync(
189189 var isLoginsPropertyDirty = user . IsPropertyDirty ( nameof ( MemberIdentityUser . Logins ) ) ;
190190 var isTokensPropertyDirty = user . IsPropertyDirty ( nameof ( MemberIdentityUser . LoginTokens ) ) ;
191191
192- if ( UpdateMemberProperties ( found , user , out var updateRoles ) )
192+ IReadOnlyList < string > propertiesUpdated = UpdateMemberProperties ( found , user , out var updateRoles ) ;
193+
194+ if ( propertiesUpdated . Count > 0 )
193195 {
194- _memberService . Save ( found ) ;
196+ // As part of logging in members we update the last login date, and, if concurrent logins are disabled, the security stamp.
197+ // If and only if we are updating these properties, we can avoid the overhead of a full save of the member with the associated
198+ // locking, property updates, tag handling etc., and make a more efficient update.
199+ if ( UpdatingOnlyLoginProperties ( propertiesUpdated ) )
200+ {
201+ await _memberService . UpdateLoginPropertiesAsync ( found ) ;
202+ }
203+ else
204+ {
205+ _memberService . Save ( found ) ;
206+ }
195207
196208 if ( updateRoles )
197209 {
@@ -222,15 +234,21 @@ public override Task<IdentityResult> UpdateAsync(
222234 }
223235
224236 scope . Complete ( ) ;
225- return Task . FromResult ( IdentityResult . Success ) ;
237+ return IdentityResult . Success ;
226238 }
227239 catch ( Exception ex )
228240 {
229- return Task . FromResult (
230- IdentityResult . Failed ( new IdentityError { Code = GenericIdentityErrorCode , Description = ex . Message } ) ) ;
241+ return IdentityResult . Failed ( new IdentityError { Code = GenericIdentityErrorCode , Description = ex . Message } ) ;
231242 }
232243 }
233244
245+ private static bool UpdatingOnlyLoginProperties ( IReadOnlyList < string > propertiesUpdated )
246+ {
247+ string [ ] loginPropertyUpdates = [ nameof ( MemberIdentityUser . LastLoginDateUtc ) , nameof ( MemberIdentityUser . SecurityStamp ) ] ;
248+ return ( propertiesUpdated . Count == 2 && propertiesUpdated . ContainsAll ( loginPropertyUpdates ) ) ||
249+ ( propertiesUpdated . Count == 1 && propertiesUpdated . ContainsAny ( loginPropertyUpdates ) ) ;
250+ }
251+
234252 /// <inheritdoc />
235253 public override Task < IdentityResult > DeleteAsync (
236254 MemberIdentityUser user ,
@@ -681,9 +699,9 @@ public override Task SetTokenAsync(MemberIdentityUser user, string loginProvider
681699 return user ;
682700 }
683701
684- private bool UpdateMemberProperties ( IMember member , MemberIdentityUser identityUser , out bool updateRoles )
702+ private IReadOnlyList < string > UpdateMemberProperties ( IMember member , MemberIdentityUser identityUser , out bool updateRoles )
685703 {
686- var anythingChanged = false ;
704+ var updatedProperties = new List < string > ( ) ;
687705 updateRoles = false ;
688706
689707 // don't assign anything if nothing has changed as this will trigger the track changes of the model
@@ -692,7 +710,7 @@ private bool UpdateMemberProperties(IMember member, MemberIdentityUser identityU
692710 || ( identityUser . LastLoginDateUtc . HasValue &&
693711 member . LastLoginDate ? . ToUniversalTime ( ) != identityUser . LastLoginDateUtc . Value ) )
694712 {
695- anythingChanged = true ;
713+ updatedProperties . Add ( nameof ( MemberIdentityUser . LastLoginDateUtc ) ) ;
696714
697715 // if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime
698716 DateTime dt = identityUser . LastLoginDateUtc == DateTime . MinValue
@@ -706,14 +724,14 @@ private bool UpdateMemberProperties(IMember member, MemberIdentityUser identityU
706724 || ( identityUser . LastPasswordChangeDateUtc . HasValue && member . LastPasswordChangeDate ? . ToUniversalTime ( ) !=
707725 identityUser . LastPasswordChangeDateUtc . Value ) )
708726 {
709- anythingChanged = true ;
727+ updatedProperties . Add ( nameof ( MemberIdentityUser . LastPasswordChangeDateUtc ) ) ;
710728 member . LastPasswordChangeDate = identityUser . LastPasswordChangeDateUtc ? . ToLocalTime ( ) ?? DateTime . Now ;
711729 }
712730
713731 if ( identityUser . IsPropertyDirty ( nameof ( MemberIdentityUser . Comments ) )
714732 && member . Comments != identityUser . Comments && identityUser . Comments . IsNullOrWhiteSpace ( ) == false )
715733 {
716- anythingChanged = true ;
734+ updatedProperties . Add ( nameof ( MemberIdentityUser . Comments ) ) ;
717735 member . Comments = identityUser . Comments ;
718736 }
719737
@@ -723,34 +741,34 @@ private bool UpdateMemberProperties(IMember member, MemberIdentityUser identityU
723741 || ( ( member . EmailConfirmedDate . HasValue == false || member . EmailConfirmedDate . Value == default ) &&
724742 identityUser . EmailConfirmed ) )
725743 {
726- anythingChanged = true ;
744+ updatedProperties . Add ( nameof ( MemberIdentityUser . EmailConfirmed ) ) ;
727745 member . EmailConfirmedDate = identityUser . EmailConfirmed ? DateTime . Now : null ;
728746 }
729747
730748 if ( identityUser . IsPropertyDirty ( nameof ( MemberIdentityUser . Name ) )
731749 && member . Name != identityUser . Name && identityUser . Name . IsNullOrWhiteSpace ( ) == false )
732750 {
733- anythingChanged = true ;
751+ updatedProperties . Add ( nameof ( MemberIdentityUser . Name ) ) ;
734752 member . Name = identityUser . Name ?? string . Empty ;
735753 }
736754
737755 if ( identityUser . IsPropertyDirty ( nameof ( MemberIdentityUser . Email ) )
738756 && member . Email != identityUser . Email && identityUser . Email . IsNullOrWhiteSpace ( ) == false )
739757 {
740- anythingChanged = true ;
758+ updatedProperties . Add ( nameof ( MemberIdentityUser . Email ) ) ;
741759 member . Email = identityUser . Email ! ;
742760 }
743761
744762 if ( identityUser . IsPropertyDirty ( nameof ( MemberIdentityUser . AccessFailedCount ) )
745763 && member . FailedPasswordAttempts != identityUser . AccessFailedCount )
746764 {
747- anythingChanged = true ;
765+ updatedProperties . Add ( nameof ( MemberIdentityUser . AccessFailedCount ) ) ;
748766 member . FailedPasswordAttempts = identityUser . AccessFailedCount ;
749767 }
750768
751769 if ( member . IsLockedOut != identityUser . IsLockedOut )
752770 {
753- anythingChanged = true ;
771+ updatedProperties . Add ( nameof ( MemberIdentityUser . IsLockedOut ) ) ;
754772 member . IsLockedOut = identityUser . IsLockedOut ;
755773
756774 if ( member . IsLockedOut )
@@ -762,48 +780,48 @@ private bool UpdateMemberProperties(IMember member, MemberIdentityUser identityU
762780
763781 if ( member . IsApproved != identityUser . IsApproved )
764782 {
765- anythingChanged = true ;
783+ updatedProperties . Add ( nameof ( MemberIdentityUser . IsApproved ) ) ;
766784 member . IsApproved = identityUser . IsApproved ;
767785 }
768786
769787 if ( identityUser . IsPropertyDirty ( nameof ( MemberIdentityUser . UserName ) )
770788 && member . Username != identityUser . UserName && identityUser . UserName . IsNullOrWhiteSpace ( ) == false )
771789 {
772- anythingChanged = true ;
790+ updatedProperties . Add ( nameof ( MemberIdentityUser . UserName ) ) ;
773791 member . Username = identityUser . UserName ! ;
774792 }
775793
776794 if ( identityUser . IsPropertyDirty ( nameof ( MemberIdentityUser . PasswordHash ) )
777795 && member . RawPasswordValue != identityUser . PasswordHash &&
778796 identityUser . PasswordHash . IsNullOrWhiteSpace ( ) == false )
779797 {
780- anythingChanged = true ;
798+ updatedProperties . Add ( nameof ( MemberIdentityUser . PasswordHash ) ) ;
781799 member . RawPasswordValue = identityUser . PasswordHash ;
782800 member . PasswordConfiguration = identityUser . PasswordConfig ;
783801 }
784802
785803 if ( member . PasswordConfiguration != identityUser . PasswordConfig )
786804 {
787- anythingChanged = true ;
805+ updatedProperties . Add ( nameof ( MemberIdentityUser . PasswordConfig ) ) ;
788806 member . PasswordConfiguration = identityUser . PasswordConfig ;
789807 }
790808
791809 if ( member . SecurityStamp != identityUser . SecurityStamp )
792810 {
793- anythingChanged = true ;
811+ updatedProperties . Add ( nameof ( MemberIdentityUser . SecurityStamp ) ) ;
794812 member . SecurityStamp = identityUser . SecurityStamp ;
795813 }
796814
797815 if ( identityUser . IsPropertyDirty ( nameof ( MemberIdentityUser . Roles ) ) )
798816 {
799- anythingChanged = true ;
817+ updatedProperties . Add ( nameof ( MemberIdentityUser . Roles ) ) ;
800818 updateRoles = true ;
801819 }
802820
803821 // reset all changes
804822 identityUser . ResetDirtyProperties ( false ) ;
805823
806- return anythingChanged ;
824+ return updatedProperties . AsReadOnly ( ) ;
807825 }
808826
809827 /// <inheritdoc />
0 commit comments