Easy Sequential numbers
Where they can be used?? In many places for example invoices, sales orders etc.
One could tell that this is an easy task and maybe that assumption is right but let me write down some real world requirements that can prove the opposite to you.
- Invoices Number Uniqueness should be quarantine in a multi user environment
- Number number should be sequential
- Invoices numbers may have series (eg AB-1001 for invoices coming from store sales , EP-1001 for invoices coming from patient treatment)
- Starting number must be configurable. (eg need to start numbering at 5000 this year, and next year start at 7000)
- There should be a way to reuse numbers of deleted invoices
- Storage (table schema) of invoice numbers should be controlled by the user
- End user API should be very flexible and easy
- Of course whatever you build has to be available in both win and web platforms
Now what you think? How much time do you need to build that?
If you have some XAF experience it will be about only some hrs work to create a reusable code that fellow devs and future projects can utilize. So in this post i am going to go through the process step by step
Step1: Check Devexpress support center
SC has samples for thousand of cases so before building anything it is most advisable to check there.
Searching revealed this one
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 (XAF)
That sample is using ExcplicitUnitOfWork (in other words explicit sql transaction) and that can guarantee requirement 1,2
Step2: Refactor the sample to fit your needs
Although the sample can be used as is in real world, has some requirements that are making it less flexible. It requires to inherit from a special BasePersistentObject class.
To overcome that we can introduce an interface instead and push all depended code to our SequenceGenerator class
public interface ISupportSequenceObject {
long Sequence { get; set; }
}
Now we can inherit from any object as you see in the above code and we only have to write the code that is inside the OnSaving method! .
The best part is that we do not even spent one minute to think about the validity of the generating seq numbers code. That is Devexpress job and they are best in doing it. What we could do is just track the issue for changes (maybe bugs will raise from other users in future ) I am happy with this refactoring so lets move on to the other requirements. |  |
Time spent 1.5hrs
Invoices numbers may have series
How about it?? Should be very easy if we understand what are SequenceGenerator does. It saves a number for a specific type in the database. So instead of a specific type we can refactor it to save a specific type and a series string. We can do that by introducing a new Prefix property to our ISupportSequenceObject interface and refactor our SequenceGenerator to save that prefix as well .
public interface ISupportSequenceObject {
long Sequence { get; set; }
string Prefix { get; }
}
our previous front end API was left as simple as it was b4 as you see bellow
Starting number must be configurable. (eg need to start numbering at 5000 this year, and next year start at 7000)
Ok that sounds very similar to our previous req. In essence the year is just a serie, so we could just change our class to
string ISupportSequenceObject.Prefix {
get { return Serie.ToString()+DateTime.Today.Year; }
}
and now our numbers are unique per serier+year!. And how can we control the starting number?
Very easy as everything in XAF!. Sequence numbers are saved using a persistent object, so the thing we need to do is just create a new Sequence number manual and the other numbers will follow since they are sequential.
var unitOfWork = new UnitOfWork();
var sequenceObjects = Enum.GetValues(typeof (SerieEnum)).Cast<SerieEnum>().Select(
serie => SequenceGenerator.CreateSequenceObject(serie.ToString() + 2012, unitOfWork));
foreach (var sequenceObject in sequenceObjects) {
sequenceObject.NextSequence = 7000;
}
unitOfWork.CommitChanges();
There should be a way to reuse numbers of deleted invoices
What does that mean? When an object that supports sequences (ISupportSequence object) is deleted we need to store the deleted sequence number and allow end user through UI to reuse it at a later time.
We already have an SequenceObject that stores the sequence number what we miss is an one to many relation with an object that stores deleted numbers. The following class will do the job
public class SequenceReleasedObject : XpandBaseCustomObject {
public SequenceReleasedObject(Session session)
: base(session) {
}
private SequenceObject _sequenceObject;
public SequenceObject SequenceObject {
get {
return _sequenceObject;
}
set {
SetPropertyValue("SequenceObject", ref _sequenceObject, value);
}
}
private long _sequence;
public long Sequence {
get {
return _sequence;
}
set {
SetPropertyValue("Sequence", ref _sequence, value);
}
}
}
Having the storage object we then need to grad the deleted ISupportSequence object and create a new SequenceReleasedObject
public class Invoice : BaseObject, ISupportSequenceObject {
public Invoice(Session session) : base(session) {
}
protected override void OnDeleted() {
base.OnDeleted();
SequenceGenerator.ReleaseSequence(this);
}
Now that we have our data stored in the database, we are going to use XAF to allow user to restore a deleted number.
I am thinking of a control (PropertyEditor in XAF terms) that could be used to render the sequence property of the ISupportSequence object. Also that control should have a button to the right, that on click is going to display a SequenceReleasedObject list . User will select something from the list and when the transaction is committed the SequenceReleasedObject value will replace ISupportSequenceObject value and will be deleted.
Sounds hard to you? In fact it is not so much. You have to remember to use the tools that XAF provides for you. Here is what I mean . XAF already has a similar property editor to render aggregated object properties
When editor's button is clicked then a detailview of the aggregated object is shown. We could just use the same editor and replace that part. Instead of displaying a detailview we could just display a listview of SequenceReleaseObjects. Exactly the same process we could follow for the web platform.
Now we have our property editors, but lets make dev job even more easier. Lets create a platform independent marker attribute that will handle the property editor type assignment.
First we create a marker attribute like
[AttributeUsage(AttributeTargets.Property)]
public class SequencePropertyAttribute : Attribute {
}
and a marker interface that will be implemented by both our editors
public interface IReleasedSequencePropertyEditor {
}
write a simple controller that will do the assignment as
public class CustomAttibutesController : WindowController {
public override void CustomizeTypesInfo(ITypesInfo typesInfo) {
base.CustomizeTypesInfo(typesInfo);
var memberInfos = typesInfo.PersistentTypes.SelectMany(info => info.OwnMembers);
foreach (var memberInfo in memberInfos) {
HandleSequencePropertyAttribute(memberInfo);
}
}
void HandleSequencePropertyAttribute(IMemberInfo memberInfo) {
var sequencePropertyAttribute = memberInfo.FindAttribute<SequencePropertyAttribute>();
if (sequencePropertyAttribute != null) {
var typeInfo = ReflectionHelper.FindTypeDescendants(XafTypesInfo.Instance.FindTypeInfo(typeof(IReleasedSequencePropertyEditor))).Single();
memberInfo.AddAttribute(new CustomAttribute("PropertyEditorType", typeInfo.FullName));
}
}
}
and of course decorate our property
private long _sequence;
[SequenceProperty]
public long Sequence {
get {
return _sequence;
}
set {
SetPropertyValue("Sequence", ref _sequence, value);
}
}
Time spent 3hr
Storage (table schema) of invoice numbers should be controlled by the user
Does the above remind you anything? It sure does to me. Reminds me the exact same problem we have with the Devexpress support center sample . It was not based on interfaces so if we extract an interface from our Sequence class (the one that store the numbers in the db) like
public interface ISequenceObject {
string TypeName { get; set; }
long NextSequence { get; set; }
}
and refactor again our SequenceGenerator to replace our references to SequenceObject persistent class with the new interface. Now the end user can control the schema of the table cause the only thing he has to do is implement the ISequenceObject to any object he wants.
Time spent 1hr
Conclusion
We have spent almost 6 hrs to implement all requirements but nothing was in vain. Cause the effort produce something very reusable. Bellow is our final, very easy to use and flexible approach.
public class Invoice : BaseObject, ISupportSequenceObject {
public Invoice(Session session) : base(session) {
}
protected override void OnDeleted() {
base.OnDeleted();
SequenceGenerator.ReleaseSequence(this);
}
protected override void OnSaving() {
base.OnSaving();
if (Session.IsNewObject(this))
Sequence = (int)SequenceGenerator.GenerateSequence(this);
}
private long _sequence;
[SequenceProperty]
public long Sequence {
get {
return _sequence;
}
set {
SetPropertyValue("Sequence", ref _sequence, value);
}
}
private SerieEnum _serie;
public SerieEnum Serie {
get {
return _serie;
}
set {
SetPropertyValue("Serie", ref _serie, value);
}
}
string ISupportSequenceObject.Prefix {
get { return Serie.ToString()+DateTime.Today.Year; }
}
}
that is the power of XAF! everything else should live in a reusable framework, like eXpand !

