Skip to content

Latest commit

 

History

History
164 lines (127 loc) · 8.18 KB

File metadata and controls

164 lines (127 loc) · 8.18 KB

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.

sequential-number

Implementation Details

Follow the steps below to add this functionality to your project:

  1. 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.
  2. Register the SequenceGeneratorProvider scoped service, configure SequenceGeneratorOptions, 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();
               };
           });
      
  3. To add sequential numbers to your business class, inherit it from the UserFriendlyIdPersistentObject class. Declare a calculated property that utilizes the base class's SequenceNumber to 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")); }
    }
       
  4. Separate sequences are created for each business object type. To generate multiple sequences for the same type based on other properties, override GetSequenceName to return a custom sequence name. In this example, the Address class uses different sequences for each Province.

    protected override string GetSequenceName() {
    	return string.Concat(ClassInfo.FullName, "-", Province.Replace(" ", "_"));
    }

Additional Information

  1. 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.

  2. You can manually set the initial sequence value by editing the Sequence table in the database or using standard XPO/XAF methods to modify Sequence objects. 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();
    }

Files to Review

Documentation

More Examples