-
Slowness with Custom Properties on .NET Security Provider
August 22, 2016 Rodrigo Peplau 1
If you ever used the standard .NET Security Provider with Sitecore, you may love how easy it is to create and use Custom Profile Properties, where data can be easily saved at user profiles. But a huge issue can emerge if you attempt to retrieve users at your database by one of these custom properties.
The Problem
Let’s say you have a custom property called Document ID, and you wish for some reason to retrieve the user that has a certain number on it – for instance, if your users can login to your website both using their Logins or Document IDs – then you may have something like this at your code:
var userFound = UserManager.GetUsers().FirstOrDefault(f => f.Profile["Document ID"] == inputDocumentId);
This simple code can bring you a big headache, because of the way .NET Security Provider builds the SQL Query responsible for executing your LINQ expression. Since all Custom Properties are stored as meta data, it is simply not directly queriable. Then what Security Provider does is one SELECT query for each user at your database, deserializing the Custom Properties on memory so it can be compared to the user input.
If you have few users at your database, which is usually the case when you are in development phase, you’ll probably not notice any issue. But after your go-live, as your user base starts growing, it will gradually get slower and slower. At the project that inspired this blog post, we had a sudden data load of more than 20k users, and as a consequence the system got impracticably slow overnight.
The Solution
One of the possible technical solutions, the one we used at the project in question, was to extend the table aspnet_Users at our Core database. That is the main table used by .NET Security Provider to store users. What we do is to create a new column Document ID, where this data will be stored in a clean format:
After that, we need to attach some code to both “user:created” and “user:updated” pipelines. This code will be responsible for updating the Document ID column when users are created or updated.
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <events> <event name="user:created"> <handler type="MyProject.Pipelines.UpdateUserProfileCache, MyProject" method="OnUserCreatedUpdated" /> </event> <event name="user:updated"> <handler type="MyProject.Pipelines.UpdateUserProfileCache, MyProject" method="OnUserCreatedUpdated" /> </event> </events> </sitecore> </configuration>
Then your code will probably look like the following. The class CustomProfileIndexManager, responsible for doing the actual Update and Select at the database is not there, but you can easily guess how to build yours.
namespace MyProject.Pipelines { public class UpdateUserProfileCache { public void OnUserCreatedUpdated(object sender, EventArgs args) { var scArgs = (SitecoreEventArgs)args; if (!scArgs.Parameters.Any()) return; var user = (System.Web.Security.MembershipUser)scArgs.Parameters.First(); // Will only act for "extranet" users var username = user.UserName; if (!username.StartsWith("extranet")) return; var scUser = Sitecore.Security.Accounts.User.FromName(username,false); // Following method is responsible for saving "Document ID" // into the respective column at the database CustomProfileIndexManager.SaveUserToCache(scUser); } } }
So your problematic code would be replaced by something like this:
// Will do a simple "SELECT * FROM aspnet_Users WHERE Document ID = 'xxxx'" var userFound = CustomProfileIndexManager.GetUserByDocumentId(inputDocumentId);
What about the existent base of users?
Of course that will only cover users that are new or updated. If you already have a certain base of users, you can build a simple script to “touch” all your users, such as this:
var startProcess = DateTime.Now; Response.Write("<font color=\"red\">Loading users...</font>"); Response.Flush(); // Get all users var users = DomainManager.GetDomain("extranet").GetUsers(); Response.Write(string.Format("OK! ({0}ms)<hr>", DateTime.Now.Subtract(startProcess).TotalMilliseconds)); Response.Flush(); // Loop into all users var counter = 0; foreach (var user in users) { var startUserProcess = DateTime.Now; counter++; if ((counter % 10) == 0) { Response.Write(string.Format("--- Total time: {0} minutes<br>", DateTime.Now.Subtract(startProcess).TotalMinutes)); Response.Flush(); } Response.Write(string.Format("User #{0} - Email: {1} - Processing...", counter, user.Profile.Email)); // Following method is responsible for saving "Document ID" // into the respective column at the database CustomProfileIndexManager.SaveUserToCache(user); Response.Write(string.Format(" OK! ({0}ms)<br/>", DateTime.Now.Subtract(startUserProcess).TotalMilliseconds)); } Response.Write(string.Format("<h3>TOTAL TIME: {0} minutes</h3>", DateTime.Now.Subtract(startProcess).TotalMinutes)); Response.Flush(); Response.End();
Categories: Development, Security Provider
Faster Sitecore for Development - All in a single include Less than a week to the Sitecore Symposium 2016 in New Orleans!
One thought on “Slowness with Custom Properties on .NET Security Provider”
Leave a Reply Cancel reply
Proudly 10x Sitecore MVP!
(2016-2025)
Localization
Recent Posts
Recent Comments
- navan on Meet MVPinny: the AI-Powered Sitecore Assistant
- Adriana on Content generation with Sitecore Connect and ChatGPT
- NAVAN on Automatic Sitecore NuGet upgrades with Powershell
- Hedipo S Menezes on Corey Peplau wrote this - WFFM conflict with Unity DI and a lesson on how Sitecore community is so amazing
- Rodrigo Peplau on ERROR [Content Testing]: Cannot find PhantomJS executable at ' (...) /data/tools/phantomjs/phantomjs.exe'. Aborting screenshot generation.
Archives
- March 2025
- January 2025
- June 2024
- April 2024
- February 2024
- December 2023
- November 2023
- August 2023
- July 2023
- January 2023
- February 2022
- December 2021
- November 2021
- March 2021
- July 2020
- February 2020
- September 2019
- July 2019
- April 2019
- March 2019
- December 2018
- February 2018
- January 2018
- November 2017
- September 2017
- August 2017
- July 2017
- March 2017
- February 2017
- November 2016
- September 2016
- August 2016
- July 2016
- April 2016
- November 2015
- September 2015
- July 2015
- April 2015
- March 2015
- February 2015
Categories
- Actions
- Active Directory
- Analytics
- Architecture
- Bug fixing
- CDP/Personalize
- ChatGPT
- Content Edition Experience
- Content Hub
- Continuous Integration
- Dev
- Development
- Environments
- Experience Editor
- Experience Forms
- Front-end
- Hackathon
- Health Check builds
- Helix
- How To
- LDAP
- MVP
- MVP Summit
- MVPinny
- Phantom JS
- Powershell
- QA
- Richtext Editor
- Rules
- Security Provider
- SIF
- Sitecore 9
- Sitecore API
- Sitecore Community
- SItecore Connect
- Sitecore Modules
- Sitecore Rocks
- Sitecore Rule Processor
- Sitecore Symposium
- SPE
- SPE-only Alliance
- SPEAK
- SUG
- Support Ticket
- TDS
- Team City
- Uncategorized
- Upgrades
- Visual Studio
- WFFM
- Workflow
- XConnect
- xDB
- XM Cloud
That was useful!
Thank you.