Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 132 additions & 123 deletions CS/XPO/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,153 +2,162 @@

This example illustrates how to implement a business object with an identifier field with autogenerated sequential values.

![](https://raw.githubusercontent.com/DevExpress-Examples/how-to-generate-a-sequential-number-for-a-persistent-object-within-a-database-transaction-xaf-e2829/17.2.8+/media/9ecee31b-58bf-11e6-80bf-00155d62480c.png)
![image](https://github.com/AndreyKozhevnikov/XAF_generate-a-sequential-number-for-a-persistent-object-within-a-database-transaction/assets/14300209/517edb5b-8412-48e1-aed8-ed5efef2f548)


## Scenario


This is a variation of the [How to generate and assign a sequential number for a business object within a database transaction, while being a part of a successful saving process](https://www.devexpress.com/Support/Center/p/E2620) XPO example, which was specially adapted for XAF applications.

In particular, for better reusability and smoother integration with the standard XAF CRUD Controllers, all the required operations to generate sequences are managed within the base persistent class automatically when a persistent object is being saved. This solution consists of several key parts:

* [Sequence](SequenceGenerator/SequenceGenerator.Module/SequenceClasses/SequenceGenerator.cs) and [SequenceGenerator](SequenceGenerator/SequenceGenerator.Module/SequenceClasses/SequenceGenerator.cs) are auxiliary classes that are primarily responsible for generating user-friendly identifiers. Take special note that the `SequenceGenerator.Initialize` method must be called during your XAF application startup for correct operation. We will describe how to do it further.
* [UserFriendlyIdPersistentObject](SequenceGenerator/SequenceGenerator.Module/SequenceClasses/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.


Check the original example description first for more information on the demonstrated scenarios and functionality.

## Implementation Details

1. Copy [SequenceGenerator](SequenceGenerator/SequenceGenerator.Module/SequenceClasses/SequenceGenerator.cs) and [UserFriendlyIdPersistentObject](SequenceGenerator/SequenceGenerator.Module/SequenceClasses/UserFriendlyIdPersistentObject.cs) files to your app.

2. Initialize SequenceGenerator with the SequenceGenerator.Initialize method:


For apps without security:

Blazor(YourSolutionName\YourSolutionName.Blazor.Server\Startup.cs):
```cs{10-13}
public class Startup {
//...
public void ConfigureServices(IServiceCollection services) {
//...
services.AddXaf(Configuration, builder => {
//...
builder.ObjectSpaceProviders
.AddXpo((serviceProvider, options) => {
//...
var dataStoreProviderManager = new DataStoreProviderManager();
var dataStoreProvider = options.GetDataStoreProvider(dataStoreProviderManager);
GenerateUserFriendlyId.Module.SequenceGenerator.Initialize(dataStoreProvider);
options.UseCustomDataStoreProvider(dataStoreProvider);
```

WinForms (YourSolutionName\YourSolutionName.Win\Startup.cs) :
```cs{11-14}
//...
public class ApplicationBuilder : IDesignTimeApplicationFactory {
public static WinApplication BuildApplication(string connectionString) {
var builder = WinApplication.CreateBuilder();
builder.UseApplication<SequenceGeneratorWindowsFormsApplication>();
builder.Modules
//...
.AddXpo((application, options) => {
options.ConnectionString = connectionString;
var dataStoreProviderManager = new DataStoreProviderManager();
var dataStoreProvider = options.GetDataStoreProvider(dataStoreProviderManager);
GenerateUserFriendlyId.Module.SequenceGenerator.Initialize(dataStoreProvider);
options.UseCustomDataStoreProvider(dataStoreProvider);
})

```
1. Copy the [SequenceGenerator](SequenceGenerator/SequenceGenerator.Module/SequenceClasses/SequenceGenerator.cs) and [UserFriendlyIdPersistentObject](SequenceGenerator/SequenceGenerator.Module/SequenceClasses/UserFriendlyIdPersistentObject.cs) files to your project.

2. Initialize `SequenceGenerator` with the `SequenceGenerator.Initialize` method.

For applications without the Security module:

* ASP.NET Core Blazor (`YourSolutionName\YourSolutionName.Blazor.Server\Startup.cs`)

```cs{10-11}
public class Startup {
//...
public void ConfigureServices(IServiceCollection services) {
//...
services.AddXaf(Configuration, builder => {
//...
builder.ObjectSpaceProviders
.AddXpo((serviceProvider, options) => {
//...
IXpoDataStoreProvider dataStoreProvider = XPObjectSpaceProvider.GetDataStoreProvider(connectionString, null, true);
GenerateUserFriendlyId.Module.SequenceGenerator.Initialize(dataStoreProvider);
})
```

* WinForms (`YourSolutionName\YourSolutionName.Win\Startup.cs`)

```cs{9-10}
//...
public class ApplicationBuilder : IDesignTimeApplicationFactory {
public static WinApplication BuildApplication(string connectionString) {
var builder = WinApplication.CreateBuilder();
builder.UseApplication<SequenceGeneratorWindowsFormsApplication>();
//...
builder.ObjectSpaceProviders
.AddXpo((application, options) => {
options.ConnectionString = connectionString;
IXpoDataStoreProvider dataStoreProvider = XPObjectSpaceProvider.GetDataStoreProvider(connectionString, null, true);
GenerateUserFriendlyId.Module.SequenceGenerator.Initialize(dataStoreProvider);
})

```

For app with Security:


Blazor(YourSolutionName\YourSolutionName.Blazor.Server\Startup.cs):
```cs{10-13}
public class Startup {
//...
public void ConfigureServices(IServiceCollection services) {
//...
services.AddXaf(Configuration, builder => {
//...
builder.ObjectSpaceProviders
.AddSecuredXpo((serviceProvider, options) => {
//...
var dataStoreProviderManager = new DataStoreProviderManager();
var dataStoreProvider = options.GetDataStoreProvider(dataStoreProviderManager);
GenerateUserFriendlyId.Module.SequenceGenerator.Initialize(dataStoreProvider);
options.UseCustomDataStoreProvider(dataStoreProvider);
```

WinForms (YourSolutionName\YourSolutionName.Win\Startup.cs) :
```cs{11-14}
//...
public class ApplicationBuilder : IDesignTimeApplicationFactory {
public static WinApplication BuildApplication(string connectionString) {
var builder = WinApplication.CreateBuilder();
builder.UseApplication<SequenceGeneratorWindowsFormsApplication>();
builder.Modules
//...
.AddSecuredXpo((application, options) => {
options.ConnectionString = connectionString;
var dataStoreProviderManager = new DataStoreProviderManager();
var dataStoreProvider = options.GetDataStoreProvider(dataStoreProviderManager);
GenerateUserFriendlyId.Module.SequenceGenerator.Initialize(dataStoreProvider);
options.UseCustomDataStoreProvider(dataStoreProvider);
})

```

For apps with the Middle Tier Security:
YourSolutionName\YourSolutionName.MiddleTierWebApi\MiddleTierSetup\WebApiApplicationSetup.cs
```cs{5-7}
public class WebApiApplicationSetup : IWebApiApplicationSetup {
public void SetupApplication(AspNetCoreApplication application) {
application.Modules.Add(new SequentalSecurityMiddle.Module.SequentalSecurityMiddleModule());
For applications with the Security module:

* ASP.NET Core Blazor (`YourSolutionName\YourSolutionName.Blazor.Server\Startup.cs`)

```cs{10-13}
public class Startup {
//...
public void ConfigureServices(IServiceCollection services) {
//...
services.AddXaf(Configuration, builder => {
//...
builder.ObjectSpaceProviders
.AddSecuredXpo((serviceProvider, options) => {
//...
IXpoDataStoreProvider dataStoreProvider = XPObjectSpaceProvider.GetDataStoreProvider(connectionString, null, true);
GenerateUserFriendlyId.Module.SequenceGenerator.Initialize(dataStoreProvider);
})
```

* WinForms (`YourSolutionName\YourSolutionName.Win\Startup.cs`)

```cs{10-11}
//...
public class ApplicationBuilder : IDesignTimeApplicationFactory {
public static WinApplication BuildApplication(string connectionString) {
var builder = WinApplication.CreateBuilder();
builder.UseApplication<SequenceGeneratorWindowsFormsApplication>();
//...
builder.ObjectSpaceProviders
.AddSecuredXpo((application, options) => {
options.ConnectionString = connectionString;
IXpoDataStoreProvider dataStoreProvider = XPObjectSpaceProvider.GetDataStoreProvider(connectionString, null, true);
GenerateUserFriendlyId.Module.SequenceGenerator.Initialize(dataStoreProvider);
})
```

For applications with the Middle Tier Security:

1. Create a custom service.

```cs
using DevExpress.ExpressApp.AspNetCore;
using DevExpress.ExpressApp.AspNetCore.WebApi;
using DevExpress.ExpressApp.Xpo;
using Microsoft.Extensions.Options;

namespace SeqXPOMiddle.MiddleTier {
public class WebApiApplicationSetup : IWebApiApplicationSetup {
public void SetupApplication(AspNetCoreApplication application) {
IOptions<DataServerSecurityOptions> options = application.ServiceProvider.GetRequiredService<IOptions<DataServerSecurityOptions>>();
IXpoDataStoreProvider dataStoreProvider = XPObjectSpaceProvider.GetDataStoreProvider(options.Value.ConnectionString, null);
GenerateUserFriendlyId.Module.SequenceGenerator.Initialize(dataStoreProvider);
}
}
}
```

2. Register this service in your `YourSolutionName.MiddleTier` project (`YourSolutionName.MiddleTier\Startup.cs`):

IOptions<DataServerSecurityOptions> options = application.ServiceProvider.GetRequiredService<IOptions<DataServerSecurityOptions>>();
IXpoDataStoreProvider dataStoreProvider = XPObjectSpaceProvider.GetDataStoreProvider(options.Value.ConnectionString, null);
GenerateUserFriendlyId.Module.SequenceGenerator.Initialize(dataStoreProvider);
```
```cs
public class Startup
//...
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton<IWebApiApplicationSetup, WebApiApplicationSetup>();
```

3. Inherit your business classes to which you want to add sequential numbers from the module's `UserFriendlyIdPersistentObject` class. Declare a calculated property that uses the `SequenceNumber` property of the base class to produce a string identifier according to the required format:

```cs
public class Contact : GenerateUserFriendlyId.Module.BusinessObjects.UserFriendlyIdPersistentObject {
[PersistentAlias("Concat('C',PadLeft(ToStr(SequentialNumber),6,'0'))")]
public string ContactId {
get { return Convert.ToString(EvaluateAlias("ContactId")); }
}
```cs
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 generated for each business object type. If you need to create multiple sequences for the same type, based on values of other object properties, override the `GetSequenceName` method and return the constructed sequence name. The `Address` class in this example uses separate sequences for each `Province` as follows:




Separate sequences are generated for each business object type. If you need to create multiple sequences for the same type, based on values of other object properties, override the `GetSequenceName` method and return the constructed sequence name. The `Address` class in this example uses separate sequences for each `Province` as follows:

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

## Additional Information

1. As an alternative, you can implement much simpler solutions at the database level or by using the built-in `DistributedIdGeneratorHelper.Generate` method. Refer to the following article for more details: [An overview of approaches to implementing a user-friendly sequential number for use with an XPO business class](https://www.devexpress.com/Support/Center/p/T567184").

2. In application with Security System, the newly generated sequence number will appear in the Detail View only after a manual refresh (in other words, it will be empty right after saving a new record), because the sequence is generated on the server side only and is not passed to the client. See the following section of the **Auto-Generate Unique Number Sequence** KB article: [Refresh the Identifier field value in UI](https://docs.devexpress.com/eXpressAppFramework/403605/business-model-design-orm/unique-auto-increment-number-generation#refresh-the-identifier-field-value-in-the-ui).

3. You can specify the initial sequence value manually: either by editing the **Sequence** table in the database or using the [standard XPO/XAF](https://docs.devexpress.com/eXpressAppFramework/113711/data-manipulation-and-business-logic/create-read-update-and-delete-data) means by manipulating the `Sequence` objects, for example:

```cs
using(IObjectSpace os = Application.CreateObjectSpace(typeof(Sequence))) {
Sequence sequence = os.FindObject<Sequence>(CriteriaOperator.Parse("TypeName=?", typeof(Contact).FullName));
sequence.NextSequence = 5;
os.CommitChanges();
}
```
1. As an alternative, you can implement much simpler solutions at the database level or by using the built-in `DistributedIdGeneratorHelper.Generate` method. Refer to the following article for more details: [An overview of approaches to implementing a user-friendly sequential number for use with an XPO business class](https://www.devexpress.com/Support/Center/p/T567184").
2. In application with Security System, the newly generated sequence number will appear in the Detail View only after a manual refresh (in other words, it will be empty right after saving a new record), because the sequence is generated on the server side only and is not passed to the client. See the following section of the **Auto-Generate Unique Number Sequence** KB article: [Refresh the Identifier field value in UI](https://docs.devexpress.com/eXpressAppFramework/403605/business-model-design-orm/unique-auto-increment-number-generation#refresh-the-identifier-field-value-in-the-ui).
3. You can specify the initial sequence value manually. For this purpose, either edit the **Sequence** table in the database or use the [standard XPO/XAF](https://docs.devexpress.com/eXpressAppFramework/113711/data-manipulation-and-business-logic/create-read-update-and-delete-data) techniques to manipulate the `Sequence` objects. For example, you can use the following code:

```cs
using(IObjectSpace os = Application.CreateObjectSpace(typeof(Sequence))) {
Sequence sequence = os.FindObject<Sequence>(CriteriaOperator.Parse("TypeName=?", typeof(Contact).FullName));
sequence.NextSequence = 5;
os.CommitChanges();
}
```

## Documentation

Expand Down
3 changes: 2 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

This example illustrates how to implement a business object with an identifier field with autogenerated sequential values.

![](https://raw.githubusercontent.com/DevExpress-Examples/how-to-generate-a-sequential-number-for-a-persistent-object-within-a-database-transaction-xaf-e2829/17.2.8+/media/9ecee31b-58bf-11e6-80bf-00155d62480c.png)
![image](https://github.com/AndreyKozhevnikov/XAF_generate-a-sequential-number-for-a-persistent-object-within-a-database-transaction/assets/14300209/2883e55f-23ec-488e-ad7e-1410968160c0)


> **Note**
> This Readme focuses on Entity Framework Core. For information on how to achieve the same functionality with XPO, see the **Readme.md** file in the [XPO solution's folder](./CS/XPO/).
Expand Down