Skip to Content

The mission: implement decent collection handling for DI API objects

The Data Interface API (DI API) offers a safe and powerful way to retrieve and manipulate data in a SAP Business One database. Currently, DI API is still implemented as a COM object. I’m quite certain that most people doing development with DI API are using some version of Visual Studio.NET and thus the .NET framework as their development platform. There might be a handful of VB6 developers out there, but they are certainly a shrinking minority. Therefore I’ve been hoping that the Dev Team would provide us with a native .NET implementation of DI API, but it seems we will have to get by with the COM implementation.

There’s one quirk in DI API that especially calls for improvement: lack of support for collection handling in the way that it is normally done in .NET development. For instance, in order to iterate through the lines of an invoice, I need to check the number of lines in the Document_Lines instance and then increment the line index in a loop with SetCurrentLine until all lines have been processed: 

Documents doc=session.GetBusinessObject(BoObjectTypes.oInvoices)
doc.GetByKey(myInvoiceDocEntry);
for(int index=0; index<doc.Lines.Count; index++)
{
 doc.Lines.SetCurrentLine(index); 
 // Do something with each row
}

Instead, I would like to be able to write something like this:

Documents doc=session.GetBusinessObject(BoObjectTypes.oInvoices)
doc.GetByKey(myInvoiceDocEntry);
foreach(Document_Lines line in doc.Lines.Browse())
{
 // Do something with each row
}

It’s not a huge difference in LOC (in this particular case, 6 against 7). However, mentally the difference is much bigger as you currently have to constantly switch your mindset on collection handling between .NET and DI API. Also, please keep in mind that the foreach loop is just one of the powerful operations that can be done on native .NET collections.

Similarly, I would like to be able to iterate through a set of business objects that have been selected with a query, such as:

Items items=session.GetBusinessObject(BoObjectTypes.oItems);
foreach(Items item in items.Browse(“select itemcode from oitm where invntitem=’Y'”))
{
  // do something with the item
}

The solution: .NET 3.5 and extension methods

I recently started to study some of the interesting new features in .NET 3.5 and Visual Studio 2008. These include such things as language integrated query (LINQ) as well as extension methods. It turned out that they could be of real benefit also in in the context of DI API development. For instance, the kind of enumeration support that I described above can rather easily be retrofitted to DI API as extension methods.

In order to put my money where my mouth is, I decided to implement the said improvements. All that is needed is actually one source-code file that has to be included in each Visual Studio 2008 project. When it is included in your project, all the DI API classes that either have a property “Browser” or a method called “SetCurrentLine” will have a new method called “Browse”. As a nice side-effect of adding the enumeration support, you can now also use LINQ for querying DI API objects.

I wanted to make the solution as open as possible as well as to guarantee maximum compatibility with previous, current and future releases of DI API. Thus, I implemented my solution as a code generator. The tool is called DI Class Extender (DICE). The full package containing binaries plus full source code can be downloaded here. You can download the binaries also from the DICE download page maintained by Profiz.
If you wish to get a quick start, you will also find a pre-generated extension file made from the Interop file for SBO 2005 SP1 PL31 on the download page. This file should be compatible with any DI API starting from 2005 SP1 PL0. However, in case there are new objects in more recent DI API versions, those will not have the extension methods unless you generate a new extension file with DICE.

 

DICE UI after startup 

Image 1.The DICE user interface immediately after startup 

 

DICE UI after generating source code

Image 2. The DICE user interface after selecting the DLL and generating the extension source code

 

When starting up, you only need to specify the path to the DI API Interop file (usually named “Interop.SAPbobsCOM.dll”) you wish to use. DICE reads the file, slices and dices it and then generates a single C# class file that contains the required extension methods. When you include this file to your project, Visual Studio will take care of adding the new extension methods to each relevant class. These extension methods will even show up in the IntelliSense dialogs, as you can see from the images below.

 

Intellisense

 

 

Intellisense 

Images 3 and 4. The extension methods shown in the Visual Studio 2008 Intellisense dialog.

You have a choice of setting the namespace for the source file when doing the generation. If you specify the same namespace as what you are using in your solution, it will work with no additions. I you use the default namespace (DICE) or something else other than the namespace of your solution, you will need to add the “using” declaration in the beginning of each source file where you wish to use the extension methods:

 

using DICE;

 

As a bonus, I also implemented another extension method – GetLine – in the classes Documents and JournalEntries. Currently, DI API only allows to get a row by index. The problem is that the index is most often the same as line number, but not always. This is because it is possible to delete, say, an order row after booking the order. Let’s say that the order originally had rows with linenumbers 0,1 and 2. If you delete the middle row, you end up with line numbers 0 and 2. SetCurrentLine(1) would get you the line with linenumber 2. In order to determine the linenumber you need, you could of course iterate through all the lines and search for the row with the current line. But without a good support for enumeration, that’ll be quite painful.

Thus, I added command that would do this for me with a single line of code:

 

Document_Lines lines=doc.GetLine(2);

 

…this will give you the line with linenumber 2 (if it exists, otherwise it will give you null) regardless of its current index.

This was only added to the classes Documents and JournalEntries because the DI API contains no metadata that would reveal the unique key field in each object. Therefore, I was unable to use generic classes in the way I did for the Browse method and had to hardcode these two methods. I chose these specific classes because they are – at least for me – the most often used classes that involve browsing through lines.

Discussion and indications for further research

If you simply want to be able to use these methods, this blogs contains all you need to know. Please note that the solution only works on Visual Studio 2008 and .NET Framework 3.5 – it will not work with earlier .NET versions.

If you want to take a deeper look into how this solution actually works or perhaps extend it for your own purposes, just download the source code. The solution is simple and straightforward. The class that does the code generation contains less than 50 lines of code. The core of the code generation solution is built on a couple of LINQ queries and a template engine called StringTemplate. The template file is less than 250 lines of code. Compare these figures with the generated extension file that is more than 2300 lines of code! If you take a look at the template file (DICETemplate.st), you will easily get a grasp on how it works and how you could add your own extensions. The generated extension file contains a couple of generic classes and a whole bunch of static wrapper methods.

I could imagine that a similar solution pattern could be useful in improving the notorious UI API. If you wish to do that, please go ahead. However, in my opinion the only decent thing to do with the UI API would be to toss it and implement a completely new solution for UI development, preferably one based on Windows Presentation Foundation.

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply