Importing and exporting data is most likely something that you have to do more than once. It can be nice to have your own module for testing purposes.
I know that the standard import/export tool in AX also lets you do many of these things, but you are a programmer, right? So you might prefer to write the code yourself.
Anyway, I just wanted to get you started with a simple example that shows how to build a simple dialog where the user can select what to import/export, the filenames, and so on.
Here is the class model diagram for the example:
This is the dialog class that is common for all the different import/export entities. You do not need to change this class when adding a new entity class. Change this class only if you would like the dialog to look different or want to add additional fields to the dialog. The methods in this class (and the rest of the classes in this example) are explained by header comments.
This class provides the starting point for the ImpExpFile
classes by presenting a dialog for the user, creating an object of the ImpExpFile
class hierarchy, and then passing the control to the created object. The following code shows you how to implement it:
class ImpExpFileDialog extends RunBase, { // Global variables FileName fileName; FileType fileType; FileEntity entity; ReadWrite readWrite; Dialog dialog; // Dialog fields DialogField dialogFileName; DialogField dialogFileType; DialogField dialogEntity; DialogField dialogReadWrite; #define.CurrentVersion(1) // Macro that contains the global variables // to be kept until the next time the user // executes this class. #localmacro.CurrentList fileName, fileType, entity, readWrite #endmacro }
The dialog
method defines the dialog that is presented to the user when the prompt method is executed. The following code shows us how the dialog
method is implemented:
public Object dialog() { // Create a new object of the DialogRunbase class dialog = new DialogRunbase("Import data from file", this); // Add the fields to the dialog and // set the initial value to be equal to // the value selected last time. dialogReadWrite = dialog.addFieldValue(typeid(ReadWrite), readWrite); dialogFileName = dialog.addFieldValue(typeid(FileName), fileName); dialogFileType = dialog.addFieldValue(typeid(FileType), fileType); dialogEntity = dialog.addFieldValue(typeid(FileEntity), entity); return dialog; }
This method, originated from the RunBase
class, is initiated when the user presses OK in the dialog, and it is used to store the user-selected values. The following code shows us how the getFromDialog
method is implemented:
public boolean getFromDialog() { fileName = dialogFileName.value(); fileType = dialogFileType.value(); entity = dialogEntity.value(); readWrite = dialogReadWrite.value(); return super(); }
The pack method is a standard method inherited from the RunBase
class. It is used to save the values that the user selects and store them until the next time the user executes the class. The following code shows us how the pack
method is implemented:
public container pack() { return [#CurrentVersion,#CurrentList]; }
The standard methods from the RunBase
class are typically used to start the main execution flow of the class. The following code shows us how the run
method is implemented:
public void run() { // Create a new object of the ImpExpFile class-hierarchy // based on the values selected in the dialog. ImpExpFile impExpFile = ImpExpFile::construct(fileType, fileName, entity, readWrite); // Start the main flow of the ImpExpFile object impExpFile.run(); }
It is the standard method inherited from the RunBase
class and is used to fetch the values that the user selected the last time he executed the class. The following example shows us how the unpack
method is implemented:
*/ public boolean unpack(container packedClass) { Version version = RunBase::getVersion(packedClass); switch (version) { case #CurrentVersion: [version, #CurrentList] = packedClass; break; default: return false; } return true; }
This is the class entry point, executed when a menu item that points to it is started, or if it is opened directly from the Application Object Tree (AOT). The following code shows us the main
method:
static void main(Args args) { // Create an object of the ImportFile class ImpExpFileDialog importFileDialog = new ImpExpFileDialog(); // Prompt the user with the fields // specified in the dialog() method if (importFileDialog.prompt()) // If the user hits the Ok-button // execute the run() method importFileDialog.run(); }
This is the main class in this class hierarchy. Any code generic to the entity classes that extend this class are set here. The class is abstract, which means that you can never create an object of this class, only its subclasses can be created (the entity classes). The following are the methods within this class:
It is the main class for the ImpExpFile
class hierarchy. The class is abstract to ensure that an object cannot be created based on it (only subclasses of this class can have objects created). The following code shows us the classDeclaration
method:
abstract class ImpExpFile { // Use the file macro to set IO flags #File // Global variables FileName fileName; FileType fileType; FileEntity entity; IO file; ReadWrite readWrite; }
It is the constructor that is used to set default values for some of the global variables. The following code shows us the new
method:
void new(FileType _fileType, FileName _fileName, FileEntity _entity, ReadWrite _readWrite) { fileType = _fileType; fileName = _fileName; entity = _entity; readWrite = _readWrite; }
This method is used to open the selected file either to read from or to write to. The following code shows us the openFile
method:
protected boolean openFile() { boolean ret = true; str rw; if (readWrite == ReadWrite::Read) rw = #io_read; else rw = #io_write; switch (fileType) { case FileType::Binary : file = new BinaryIo(filename, rw); break; case FileType::Comma : file = new CommaIO(filename, rw); break; default : ret = false; break; } return ret; }
This method is used to initialize the file that is to be read. The following code shows us the readFile
method:
protected void readFile() { if(!this.openFile()) throw error("Unable to open the selected file"); file.inFieldDelimiter(';'), file.inRecordDelimiter(' '), }
This is the main execution flow of the ImpExpFile
. It decides whether to read to or write from the file and catch any exceptions that might occur. The following code shows us the run
method in it:
void run() { try { // Use this function to make sure that // the mouse-pointer changes to a timeglass. startLengthyOperation(); if (readWrite == ReadWrite::read) this.readFile(); else this.writeFile(); } catch (Exception::Deadlock) { retry; } catch (Exception::Error) { error(strfmt("An error occured while trying to read the file %1 into the %2 entity", filename, enum2str(entity))); } // Mous-pointer can switch back // to normal again. endLengthyOperation(); }
This method is used to initialize the file that is to be written to. The following code shows us the writeFile
method in it:
protected void writeFile() { if(!this.openFile()) throw error("Unable to open the selected file"); file.outFieldDelimiter(';'), file.outRecordDelimiter(' '), }
Create an object for one of the subclasses based on the user input in the dialog. The following code shows us the construct
method:
static ImpExpFile construct(FileType _fileType, FileName _filename, FileEntity _entity, ReadWrite _readWrite) { ImpExpFile impExpFile; switch(_entity) { case FileEntity::Cars : impExpFile = new ImpExp_Cars(_fileType, _filename, _entity, _readWrite); break; case FileEntity::Rentalts : impExpFile = new ImpExp_Rentals(_fileType, _filename, _entity, _readWrite); break; } return impExpFile; }
This is one of the entity classes that I have created to demonstrate how you can use this example framework. It allows for the export and import of data from and to the CarTable
respectively. Note that it only supports the insertion of new records into the CarTable
, not updating. You can, of course, add that part yourself if you have read the previous chapter. The following are the methods within this class.
It's an empty class declaration as there are no global variables in this class, and it is as follows:
class ImpExp_Cars extends ImpExpFile { }
This example shows a hardcoded way of reading the file that is to be used to insert new records into the CarTable
. Each line in the file is read into a container. Each field is fetched from the container using the conpeek()
function:
void readFile() { CarTable carTable; container con; super(); con = file.read(); if (conlen(con) != 5) throw error("The file has an illegal format"); // Read the file as long as the file is ok. // The status changes when the cursor hits // the end of the file. while (file.status() == IO_Status::Ok) { carTable.clear(); carTable.CarId = conpeek(con, 1); carTable.CarBrand = conpeek(con, 2); carTable.Model = conpeek(con, 3); carTable.ModelYear = conpeek(con, 4); carTable.Mileage = conpeek(con, 5); if (carTable.validateWrite()) carTable.insert(); con = file.read(); } }
It is used to write information from the carTable
to the file, and it is implemented as shown in the following code:
void writeFile() { CarTable carTable; container con; super(); while select carTable { con = conins(con, 1, carTable.CarId, carTable.CarBrand, carTable.Model, carTable.ModelYear, carTable.Mileage); file.writeExp(con); } }
This entity class is written to export and import all fields from the rental table. It can also be rewritten so that it is generic and it works with all tables; but for now I just want to show you how to use the DictTable
and DictField
classes while exporting and importing. The following are the methods within this class.
It is an empty class declaration as there are no global variables in this class, and is as follows:
class ImpExp_Rentals extends ImpExpFile { }
This method uses the DictTable
and DictField
classes to find all the fields in the RentalTable
and insert the data from the file to the matching fields in the RentalTable
. The following code shows us the readFile
method in it:
void readFile() { RentalTable rentalTable; container con; DictTable dTable = new DictTable(tablenum(RentalTable)); DictField dField; int i, fields, field; super(); con = file.read(); while (file.status() == IO_Status::Ok) { for (i=1; i <= conlen(con); i++) { field = dTable.fieldCnt2Id(i); dField = dTable.fieldObject(field); switch (dField.baseType()) { case Types::Guid : rentalTable.(field) = str2guid(conpeek(con, i)); break; case Types::Int64 : rentalTable.(field) = str2Int64(conpeek(con, i)); break; case Types::Date : rentalTable.(field) = str2Date(conpeek(con, i),321); break; default : rentalTable.(field) = conpeek(con, i); break; } } if (rentalTable.validateWrite()) rentalTable.insert(); con = file.read(); } }
This method uses the DictTable
and DictField
class to loop through all the fields in RentalTable and write their content to the file. The following code shows us the writeFile
method:
void writeFile() { RentalTable rentalTable; container con; int fields, i, fieldId; DictTable dt = new DictTable(tablenum(RentalTable)); super(); while select rentalTable { con = connull(); fields = dt.fieldCnt(); for (i=1; i<=fields; i++) { fieldId = dt.fieldCnt2Id(i); con = conins(con, i, rentalTable.(fieldId)); } file.write(con); } }
18.191.88.249