XAF - How to generate a sequential number for a persistent object within a database transaction with XPO
This example illustrates how to implement a business object with an identifier field with autogenerated sequential values.
Follow the steps below to add this functionality to your project:
-
Copy the following files to your project:
- SequenceGenerator.cs contains classes responsible for generating user-friendly identifiers.
- UserFriendlyIdPersistentObject.cs is a base persistent class that subscribes to XPO's Session events and delegates calls to the core classes above. To get the described functionality in your project, inherit your own business classes from this base class.
-
Register the
SequenceGeneratorProviderscoped service, configureSequenceGeneratorOptions, and specify the method that will be used to retrieve the Connection String from the database:-
ASP.NET Core Blazor without multi-tenancy (
YourSolutionName\YourSolutionName.Blazor.Server\Startup.cs)public class Startup { //... public void ConfigureServices(IServiceCollection services) { //... services.AddXaf(Configuration, builder => { //... builder.Services.AddScoped<SequenceGeneratorProvider>(); builder.Services.Configure<SequenceGeneratorOptions>(opt => { opt.GetConnectionString = (serviceProvider) => { return Configuration.GetConnectionString("ConnectionString"); }; }); -
WinForms without multi-tenancy (
YourSolutionName\YourSolutionName.Win\Startup.cs)//... public class ApplicationBuilder : IDesignTimeApplicationFactory { public static WinApplication BuildApplication(string connectionString) { var builder = WinApplication.CreateBuilder(); builder.UseApplication<SequenceGeneratorWindowsFormsApplication>(); //... builder.Services.AddScoped<SequenceGeneratorProvider>(); builder.Services.Configure<SequenceGeneratorOptions>(opt => { opt.GetConnectionString = (serviceProvider) => { return connectionString; }; }); -
Applications with Middle Tier Security without multi-tenancy (
YourSolutionName.MiddleTier\Startup.cs):public class Startup //... public void ConfigureServices(IServiceCollection services) { services.AddScoped<SequenceGeneratorProvider>(); services.Configure<SequenceGeneratorOptions>(opt => { opt.GetConnectionString = (serviceProvider) => { var options = serviceProvider.GetRequiredService<IOptions<DataServerSecurityOptions>>(); return options.Value.ConnectionString; }; }); -
ASP.NET Core Blazor multi-tenant applications (
YourSolutionName\YourSolutionName.Blazor.Server\Startup.cs)public class Startup { //... public void ConfigureServices(IServiceCollection services) { //... services.AddXaf(Configuration, builder => { //... builder.Services.AddScoped<SequenceGeneratorProvider>(); builder.Services.Configure<SequenceGeneratorOptions>(opt => { opt.GetConnectionString = (serviceProvider) => { return serviceProvider.GetRequiredService<IConnectionStringProvider>().GetConnectionString(); }; }); -
WinForms multi-tenant applications (
YourSolutionName\YourSolutionName.Win\Startup.cs)//... public class ApplicationBuilder : IDesignTimeApplicationFactory { public static WinApplication BuildApplication(string connectionString) { var builder = WinApplication.CreateBuilder(); builder.UseApplication<SequenceGeneratorWindowsFormsApplication>(); //... builder.Services.AddScoped<SequenceGeneratorProvider>(); builder.Services.Configure<SequenceGeneratorOptions>(opt => { opt.GetConnectionString = (serviceProvider) => { return serviceProvider.GetRequiredService<IConnectionStringProvider>().GetConnectionString(); }; }); -
Multi-tenant applications with Middle Tier Security (
YourSolutionName.MiddleTier\Startup.cs):public class Startup //... public void ConfigureServices(IServiceCollection services) { services.AddScoped<SequenceGeneratorProvider>(); services.Configure<SequenceGeneratorOptions>(opt => { opt.GetConnectionString = (serviceProvider) => { return serviceProvider.GetRequiredService<IConnectionStringProvider>().GetConnectionString(); }; });
-
-
To add sequential numbers to your business class, inherit it from the
UserFriendlyIdPersistentObjectclass. Declare a calculated property that utilizes the base class'sSequenceNumberto generate a string identifier in the desired format.public class Contact : GenerateUserFriendlyId.Module.BusinessObjects.UserFriendlyIdPersistentObject { [PersistentAlias("Concat('C',PadLeft(ToStr(SequentialNumber),6,'0'))")] public string ContactId { get { return Convert.ToString(EvaluateAlias("ContactId")); } }
-
Separate sequences are created for each business object type. To generate multiple sequences for the same type based on other properties, override
GetSequenceNameto return a custom sequence name. In this example, theAddressclass uses different sequences for eachProvince.protected override string GetSequenceName() { return string.Concat(ClassInfo.FullName, "-", Province.Replace(" ", "_")); }
-
In applications with the Security System, a new sequence number appears in the Detail View only after a manual refresh because it is generated server-side and not immediately sent to the client. For more details, see the following help topic: Refresh the Identifier Field Value in the UI.
-
You can manually set the initial sequence value by editing the
Sequencetable in the database or using standard XPO/XAF methods to modifySequenceobjects. For instance, you can use the following code:using(IObjectSpace os = Application.CreateObjectSpace(typeof(Sequence))) { Sequence sequence = os.FindObject<Sequence>(CriteriaOperator.Parse("TypeName=?", typeof(Contact).FullName)); sequence.NextSequence = 5; os.CommitChanges(); }