You can use a dynamic node provider or implement ISiteMapNodeProvider to programatically supply your own data (including custom ids) from any source.
Dynamic node providers can be added without using an external dependency injection container, but you need to add a "template" node either in XML or using .NET attributes to attach the provider to (see the above link).
public class StoryDynamicNodeProvider : DynamicNodeProviderBase
{
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
// Entities would be your entity framework context class
// or repository.
using (var entities = new Entities())
{
// Create a node for each blog post
foreach (var story in entities.Stories)
{
DynamicNode dynamicNode = new DynamicNode();
dynamicNode.Title = story.Title;
// The key of the node that this node will be the child of.
// This works best if you explicitly set the key property/attribute
// of the parent node.
dynamicNode.ParentKey = "Home";
dynamicNode.Key = "Story_" + story.Id;
dynamicNode.Controller = "Story";
dynamicNode.Action = "Details";
// Add the "id" (or any other custom route values)
dynamicNode.RouteValues.Add("id", story.Id);
yield return dynamicNode;
}
}
}
}
Using ISiteMapNodeProvider you can build the entire SiteMap structure including the root node, but currently it requires using an external DI container to inject a custom implementation.
There is an example here of how you could implement ISiteMapNodeProvider yourself. Here is an example of injecting a custom implementation using SimpleInjector.
Do note that there is currently a limitation in the 10s of thousands for the total number of nodes on a server because they are cached in memory, so if you have that many nodes using preservedRouteParameters is a better choice. However, it has a limitation that the individual URLs can only appear in the SiteMapPath, but not in the Menu, SiteMap, or XML Sitemap for search engines.