This version 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 example is primarily intended for XAF applications, because all the required operations to generate sequences are managed within the base persistent class. However, this code can be used in a regular non-XAF application based on XPO, as well.
For more convenience, this solution is organized as a reusable module - GenerateUserFriendlyId.Module, which you may want to copy into your project 'as is' and then add it as required via the Module or Application designers. This module consists of several key parts:
1. Sequence, SequenceGenerator, and SequenceGeneratorInitializer - auxiliary classes that take the main part in generating user-friendly identifiers.
Take special note that the last two classes need to be initialized in the ModuleUpdater and ModuleBase descendants of your real project.
2. UserFriendlyIdPersistentObject - a base persistent class that subscribes to XPO's Session events and delegates calls to the core classes above.
3. IUserFriendlyIdDomainComponent - a base domain component that should be implemented by all domain components that require the described functionality. Take special note that such derived components must still use the BasePersistentObject as a base class during the registration, e.g.:
[C#]XafTypesInfo.Instance.RegisterEntity("Document",typeof(IDocument),typeof(BasePersistentObject));
4. Address, Contact, and IDocument are business objects that demonstrate the use of the described functionality for XPO and DC respectively.
Take special note that a required format for the user-friendly identifier property in these end classes is defined within an aliased property (AddressId in the example below) by concatenation of a required constant or dynamic value with the SequentialNumber property provided by the base UserFriendlyIdPersistentObject class. So, if you need to have a different format, modify the PersistentAliasAttribute expression as your business needs dictate:
[C#][PersistentAlias("concat('A', ToStr(SequentialNumber))")]publicstringAddressId{get{returnConvert.ToString(EvaluateAlias("AddressId"));}}
IMPORTANT NOTES
1. If your connection string contains the Password parameter, then rework the SequenceGeneratorInitializer.Initialize method to not use the XafApplication.ConnectionString or XafApplication.Connection.ConnectionString properties, because XAF encrypts the specified password for safety reasons. Instead, either specify the connection string directly or read it from the configuration file.
2. The sequential number functionality shown in this example does not work with shared parts (a part of the Domain Components (DC) technology) in the current version, because it requires a custom base class, which is not allowed for shared parts.
3. This solution is not yet tested in the middle-tier and SecuredObjectSpaceProvider scenario and most likely, it will have to be modified to support its specifics.
4. As an alternative, you can use a more simple solution that is using the DistributedIdGeneratorHelper.Generate method as shown in the FeatureCenter demo ("%Public%\Documents\DXperience 13.X Demos\eXpressApp Framework\FeatureCenter\CS\FeatureCenter.Module\KeyProperty\GuidKeyPropertyObject.cs" ) or at How to generate a sequential and user-friendly identifier field within a business class
Question Comments
Added By: Mr. Murat YILMAZ at: 10/7/2012 9:15:18 AM
Thanks for the example. I developed ISequentialNumber domain component which based on this article and code. Now this sequential number functonatility shon in this example work with domain components and shared parts.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Base;
using DevExpress.Xpo;
using DevExpress.Xpo.Metadata;
using DevExpress.ExpressApp;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Persistent.Validation;
using MyWay.Xaf.Module;
using MyWay.Xaf.Utils.Sequence;
namespace MyWay.Xaf.Module
{
[DomainComponent]
[NavigationItem]
public interface ISequentialNumber
{
#region PROPERTIES
#region SequentialNo
[Persistent]
string SequentialNo
{
get;
}
#endregion SequentialNo
#region SequentialNumber
[System.ComponentModel.Browsable(false)]
[Indexed(Unique = false)]
long SequentialNumber
{
get ;
set;
}
#endregion SequentialNumber
#endregion PROPERTIES
}
[DomainLogic(typeof(ISequentialNumber))]
public class ISequentialNumberLogic
{
public static string Get_SequentialNo(ISequentialNumber instance)
{
return "asd" + instance.SequentialNumber.ToString();
}
private static object syncRoot = new object();
private static SequenceGenerator sequenceGenerator;
#region METHODS
#region PUBLIC METHODS
#region GenerateSequence
public static void GenerateSequence(ISequentialNumber instance)
{
lock (syncRoot)
{
Dictionary<string, bool> typeToExistsMap = new Dictionary<string, bool>();
foreach (object item in ((XPBaseObject)instance).Session.GetObjectsToSave())
{
typeToExistsMap[((XPBaseObject)instance).Session.GetClassInfo(item).FullName] = true;
}
if (sequenceGenerator == null)
{
sequenceGenerator = new SequenceGenerator(typeToExistsMap);
}
SubscribeToEvents(((XPBaseObject)instance).Session);
OnSequenceGenerated(sequenceGenerator.GetNextSequence(((XPBaseObject)instance).ClassInfo), instance);
}
}
#endregion GenerateSequence
#endregion PUBLIC METHODS
#region PROTECTED METHODS
#region OnSaving
public static void OnSaving(ISequentialNumber instance)
{
try
{
//base.OnSaving();
if (((XPBaseObject)instance).Session.IsNewObject(instance) && !typeof(NestedUnitOfWork).IsInstanceOfType(((XPBaseObject)instance).Session))
{
GenerateSequence(instance);
}
}
catch
{
CancelSequence(((XPBaseObject)instance).Session);
throw;
}
}
#endregion OnSaving
#endregion PROTECTED METHODS
#region INTERNAL METHODS
#endregion INTERNAL METHODS
#region PRIVATE METHODS
#region AcceptSequence
private static void AcceptSequence(Session session)
{
lock (syncRoot)
{
if (sequenceGenerator != null)
{
try
{
sequenceGenerator.Accept();
}
finally
{
CancelSequence(session);
}
}
}
}
#endregion AcceptSequence
#region CancelSequence
private static void CancelSequence(Session session)
{
lock (syncRoot)
{
UnSubscribeFromEvents(session);
if (sequenceGenerator != null)
{
sequenceGenerator.Close();
sequenceGenerator = null;
}
}
}
#endregion CancelSequence
#region Session_AfterCommitTransaction
private static void Session_AfterCommitTransaction(object sender, SessionManipulationEventArgs e)
{
AcceptSequence(e.Session);
}
#endregion Session_AfterCommitTransaction
#region Session_AfterRollBack
private static void Session_AfterRollBack(object sender, SessionManipulationEventArgs e)
{
CancelSequence(e.Session);
}
#endregion Session_AfterRollBack
#region Session_FailedCommitTransaction
private static void Session_FailedCommitTransaction(object sender, SessionOperationFailEventArgs e)
{
CancelSequence((Session)sender);
}
#endregion Session_FailedCommitTransaction
#region SubscribeToEvents
private static void SubscribeToEvents(Session session)
{
if (!(session is NestedUnitOfWork))
{
session.AfterCommitTransaction += Session_AfterCommitTransaction;
session.AfterRollbackTransaction += Session_AfterRollBack;
session.FailedCommitTransaction += Session_FailedCommitTransaction;
}
}
#endregion SubscribeToEvents
#region UnSubscribeFromEvents
private static void UnSubscribeFromEvents(Session session)
{
if (!(session is NestedUnitOfWork))
{
session.AfterCommitTransaction -= Session_AfterCommitTransaction;
session.AfterRollbackTransaction -= Session_AfterRollBack;
session.FailedCommitTransaction -= Session_FailedCommitTransaction;
}
}
#endregion UnSubscribeFromEvents
#region OnSequenceGenerated
private static void OnSequenceGenerated(long newId, ISequentialNumber instance)
{
instance.SequentialNumber = newId;
}
#endregion OnSequenceGenerated
#endregion PRIVATE METHODS
#endregion METHODS
}
}
Hi Mr. Murat YILMAZ, Can yo provide some example to use the class. I am looking it for shared parts
Added By: Andrew Bingham 2 at: 7/8/2013 3:38:51 AMI implemented this OK in a Solution
When I eimplmented it in a different solution I got an Exception
"Message: Value cannot be null.
Parameter name: Application"
thrown at:
public static void Initialize() {
Guard.ArgumentNotNull(Application, "Application");
@Andrew: You forgot to add the following code:
public override void Setup(XafApplication application) {
base.Setup(application);
SequenceGeneratorInitializer.Register(application);
}
You will not experience this and similar difficulties if you simply add the reusable module into your solution as per instructions above.
Added By: Carlitos at: 8/23/2014 6:46:05 PMHi,
Is this now supported in Middle-Tier XAF applications?
Thanks,
Carlitos
Added By: Dennis (DevExpress Support) at: 8/25/2014 4:38:12 AMWe have not performed additional R&D and testing of this particular example in a middle-tier scenario. Moreover, I see technical problems using this particular solution in this configuration, because here we use ExplicitUnitOfWork, which implies that it will be a sole owner of the database connection. Probably, for this configuration, it is better to stick to a standard solution, e.g. using database triggers.
Added By: Carlitos at: 8/25/2014 9:34:23 AMHi Dennis,
But how does the Middle-tier handle database connections? I thought it would do some sort of connection pooling and manage all client connections?
Why do you think that Triggers are a good alternative?
Carlos
Added By: Dennis (DevExpress Support) at: 8/26/2014 6:14:27 AMBy default, the middle-tier app server uses ThreadSafeDataLayer to effectively handle requests from multiple clients at the same time. Connection pooling may be used here, but this is not managed by us by default and is rather controller by ADO.NET transparently for a consumer. My main concern here was that in such a multi-user environment it will not be possible to exclusively lock the connection by one user.
A solution at the database level looks more universal as it works at the database level and is irrelevant to whether the app server is used or not.
In XAF application I have about 25 various business objects that need to generate unique user friendly sequence numbers in when saved. For example Customer Number, Invoice Number, Order Number, etc. Many users are going to work concurrently and everything should work in transaction.
Which approach should I use, the one in E2620 or E2829?.
I felt E2620 a bit better as I need not to inherit my classes (25 of them) from UserFriendlyIdPersistentObject as shown in E2829.
But approach in E2829 is said to be primarily for XAF applications
Added By: Dennis (DevExpress Support) at: 4/15/2015 4:47:25 AM@Vishwas: Both E2620 and E2829 demonstrate the same solution using ExplicitUnitOfWork. E2829 is just a bit better integrated with the XAF infrastructure.
If you do not want to inherit from a common base class, I suggest you consider using How to generate a sequential and user-friendly identifier field within a business class, which is simpler to implement and provides similar capabilities without using ExplicitUnitOfWork.