asna.com Sign In
The AVR for .NET MemoryFile gets program described capabilities 

ASNA Visual RPG (AVR) for .NET has offered its MemoryFile since the product’s introduction in 2003. The MemoryFile is an RPG interface that wraps the System.Data.DataSet. The DataSet is a container for one or more System.DataTables. The DataSet and its DataTable(s) provide a great way to create typed structures in memory. For several years, the definition of a MemoryFile required the reference of a specific physical or logic file (with the FileDesc() keyword). This defined a DataTable with exactly the same structure as the defining physical or logical. All was fine if what you wanted was a DataTable with exactly that structure. Alas, if you needed to add or remove fields from that DataTable, you had your work cut out for you.

We’ve published several work-arounds over the years to solve this challenge (including this article at http://devnet.asna.com/KB/Pages/311.aspx). However, most of these work-arounds were awkward hacks that left a queasy feeling in the pit of any respectable programmer’s stomach.

Effective with AVR 9.144, ASNA R&D slipped in a substantial enhancement to the MemoryFile that has pretty much flown under the wire ever since. We’ve discussed it at our ASNApalooza developer conference, but the enhancement remains a best-kept secret. This enhancement was introduced primarily to accommodate AVR Classic to .NET fat client migration.  However, as you’ll quickly see, this enhancement brings tons of value to any style of AVR for .NET coding.

If you feel entirely comfortable with all aspects of AVR’s MemoryFile, jump ahead to the “Enough already! What’s new?” subhead. Otherwise, read on.

DataSets and DataTables revisited

The DataTable can zero or more records in it, so .NET developers often use the DataSet and its DataTable(s) as an in-memory data store. For example, the following console application shows a way to instance a MemoryFile and write ten records (this simple code includes no error handling and assumes there are at least ten records in the CMastNewL2 file) to the MemoryFile:

0001  Using System

0002  Using System.Collections

0003  Using System.Text

0004  Using System.Data

0005 

0006  BegClass Program

0007      BegSr Main Shared(*Yes) Access(*Public) Attributes(System.STAThread())

0008          DclSrParm args Type(*String) Rank(1)            

0009         

0010          ( *New Test() ).Run()

0011         

0012          Console.WriteLine( "Press any key to continue..." )

0013          Console.ReadKey()

0014      EndSr

0015  EndClass

0016 

0017  BegClass Test

0018      DclDB pgmDB DBName( "*Public/DG Net Local" )

0019 

0020      DclDiskFile  Cust                   +

0021            Type( *Input  )               +

0022            Org( *Indexed )               +

0023            Prefix( Cust_ )               +

0024            File( "Examples/CMastNewL2" ) +

0025            DB( pgmDB )                   +

0026            ImpOpen( *No ) 

0027           

0028      DclMemoryFile CustMem                   +

0029            DBDesc( "*Public/DG Net Local" )  +

0030            Prefix( Cust_ )                   +

0031            FileDesc( "Examples/CMastNewL2" ) +

0032            RnmFmt( CustMemR )

0033 

0034      BegSr Run Access( *Public )

0035          DclFld Message Type( *String )

0036 

0037          Message = "There are {0} rows in CustMem's zeroth DataTable"

0038     

0039          Connect pgmDB

0040          Open Cust

0041         

0042          Do FromVal( 1 ) ToVal( 10 )

0043              Read Cust

0044              Write CustMem       

0045          EndDo

0046         

0047          Console.WriteLine( Message, CustMem.DataSet.Tables[ 0 ].Rows.Count )

0048         

0049          ListColumnNamesAndType()

0050          ListColumnValue()

0051         

0052          Close Cust

0053          Disconnect pgmDB

0054      EndSr   

0055 

0056      BegSr ListColumnNamesAndType

0057          DclFld dt Type( DataTable )

0058         

0059          dt = CustMem.DataSet.Tables[ 0 ]

0060         

0061          ForEach Col Type( DataColumn ) Collection( dt.Columns )

0062              Console.WriteLine( Col.ColumnName + " " + Col.DataType )     

0063          EndFor   

0064      EndSr

0065     

0066      BegSr ListColumnValue

0067          DclFld dt Type( DataTable )

0068         

0069          dt = CustMem.DataSet.Tables[ 0 ]

0070         

0071          ForEach Row Type( DataRow ) Collection( dt.Rows )

0072              Console.WriteLine( Row[ "CMCustNo" ].ToString() ++

0073                  " " + Row[ "CMName" ].ToString() )      

0074          EndFor       

0075      EndSr  

0076  EndClass

Figure 1. Example program writing to a MemoryFile

This example code is using a console application project type. If you haven’t yet used a console application, considering adding it to your .NET kitbag. It is perfect for testing things. It removes all unnecessary forms and other environmental things and lets you focus simply on the code. If you’re new to console applications, they do have what appears to be a minor inconvenience. A console app needs a shared subroutine named “Main”; this convention determines the entry point for the app. The apparently inconvenient part is that because Main is shared, other members of its parent class must also be shared. To avoid this, I always code a second class (usually named Test), add a public Run method, and then instance the class and run its Run() method with this single line of code:

0010          ( *New Test() ).Run()

In Figure 1, the following code declares a memory file

0028      DclMemoryFile CustMem                   +

0029            DBDesc( "*Public/DG Net Local" )  +

0030            Prefix( Cust_ )                   +

0031            FileDesc( "Examples/CMastNewL2" ) +

0032            RnmFmt( CustMemR )

The CustMem memory file field definitions are based on the physical or logical file specified with the FileDesc() keyword in line 31. This internal file definition is implemented through a DataTable. The MemoryFile implicitly defines an internal DataSet and this DataSet has a single DataTable—which has exactly the same fields as the underlying physical or logical file. While DataSets can optionally have more than one DataTable, the DataSet of a MemoryFile always has just the one DataTable (because of .NET’s zero-based indexing numbering scheme, this means this single table is the zeroth table).

The following code lists each column in a MemoryFile’s DataTable:

0001      BegSr ListColumnNameAndType

0002          DclFld dt Type( DataTable )

0003         

0004          dt = CustMem.DataSet.Tables[ 0 ]

0005         

0006          ForEach Col Type( DataColumn ) Collection( dt.Columns )

0007              Console.WriteLine( Col.ColumnName + " " + Col.DataType )     

0008          EndFor   

0009      EndSr

Figure 2. List all columns in a DataTable

Line 4, from the code above, gets a reference to the zeroth DataTable (the only DataTable), in the MemoryFile’s DataSet. This isn’t necessary, but using the variable name “dt” helps keep much of the code shorter in subsequent lines.

The following code lists the values of a couple of columns in a MemoryFile’s DataTable:

0001      BegSr ListColumnValue

0002          DclFld dt Type( DataTable )

0003         

0004          dt = CustMem.DataSet.Tables[ 0 ]

0005         

0006          ForEach Row Type( DataRow ) Collection( dt.Rows )

0007              Console.WriteLine( Row[ "CMCustNo" ].ToString() ++

0008                  " " + Row[ "CMName" ].ToString() )      

0009          EndFor       

0010      EndSr

Figure 3. List two column values for each row in a MemoryFile’s DataTable

The good news with a DataTable is that each column does indeed have a type, but that column isn’t strongly typed. Thus, as lines 7 and 8 above show, when you fetch values out of a DataTable’s columns, you must cast the values appropriately. The code above is using the field name (which isn’t case sensitive) to get the columns’ values—however you could, if you knew it, use the column’s ordinal position here as well.

You want to be careful when writing rows to a MemoryFile. The MemoryFile’s DataSet represents an in-memory data store, so whatever you write to the MemoryFile is consuming memory. Avoid writing many records to the MemoryFile! How many is too many? There isn’t a hard and fast number. You can successfully write more rows to a MemoryFile in a fat Windows client program (because each user’s PC has its own memory) than you can write to a MemoryFile in a browser-based app (because all users are sharing memory in the same in-process space on the Web server.

Enough already! What’s new?  

Lines 28-32 from Figure 1 are repeated below:

0028      DclMemoryFile CustMem                   +

0029            DBDesc( "*Public/DG Net Local" )  +

0030            Prefix( Cust_ )                   +

0031            FileDesc( "Examples/CMastNewL2" ) +

0032            RnmFmt( CustMemR )

This declaration creates a memory file with exactly the same structure as the physical or logic file specified in line 31. If you need a file defined exactly the same as the physical or logical file, this way to define a MemoryFile works just fine. The problem is with adding or removing fields. While that challenge can be resolved, the ways to do so are counter-intuitive and require more code than ever seemed reasonable.

For example purposes, let’s assume that we need is a MemoryFile with a column for CMastNewL2’s CMCustNo and CMName fields, and also an Email field (the contents of which will be populated from a source other than CMastNewL2). Using the new MemoryFile enhancement, which I’m calling a program described memory file, that MemoryFile would be defined like this:

0001      DclMemoryFile CustMem

0002          DclRecordFormat Customers

0003          DclRecordFld    Cust_CMCustNo Type( *Packed ) Len( 9,0 )

0004          DclRecordFld    Cust_CMName   Type( *Char ) Len( 40 )

0005          DclRecordFld    Cust_Email    Type( *Char ) Len( 40 )

Figure 4. Program described memory file

There are several things to notice here:

·        The syntax is much cleaner and concise than with an externally described MemoryFile.

·        A program described MemoryFIle needs to have a “record format” specified with the DclRecordFormat opcode. Under the covers, this value is specifying the name of the underlying zeroth DataTable created for the MemoryFile’s DataSet.

·        You can have as many fields declared with DclRecordFld as necessary.

·        The program described MemoryFile doesn’t directly support the concept of a Prefix. Thus, if you want columns named with a prefix you must explicitly specify that prefix in the column name. If the column names match exactly the column names from the DclDiskFile, then the movement of field values from the last record read to the MemoryFile is implicit with a Write to the MemoryFile. See below for what to do if the column names aren’t the same.

Figure 5 below is an example of how to assign a field value in the MemoryFile that isn’t contained in the underlying DclDiskFile. This code uses the MemoryFile described in Figure 4. In this case, Cust_Email doesn’t appear in the CMastNewL2 disk file.

Having read a record from CMastNewL2, the Write operation causes Cust_CMCustNo and Cust_CMName to be implicitly populated. Line 3, though, shows that the Cust_Email value must be explicitly populated.

0001          Do FromVal( 1 ) ToVal( 10 )

0002              Read Cust

0003              Cust_Email = "rp@asna.com"

0004              Write CustMem       

0005          EndDo

Figure 5. Working with fields in a program described memory file

Once defined, a program described MemoryFile behaves exactly the same as the older, externally described MemoryFile did. The program described MemoryFile works with AVR for .NET 9.1.44 and up. Once I realized the power of the program described MemoryFIle, I never looked back. It will be the rare case for me to use an externally described MemoryFile in the future.

Sad trombone

During the research for this article, I uncovered a frustrating little bug in the program described MemoryFile. Because it was originally added to AVR for .NET for use with the AVR Classic to AVR for .NET Upgrade Assistant, it is currently constrained to field definitions of type *Packed, *Zoned, *Char, and *TimeStamp. Defining columns with any other data type will cause unpredictable results. This issue has been fixed by our R&D team--but it won't surface in a generally available version of AVR for a couple of weeks. You can check with tech support to see when case number 13145 is resolved in the currently available AVR build. Until the fix is available, though, the program described MemoryFile is still quite serviceable; 90% of the fields you’ll need to add will usually be one of the four data types currently supported.

 

Related Articles
Article Downloads
 
Keywords:
MemoryFile, Memory file, DataSet, DataTable
Article ID: 497 
Category: ASNA Visual RPG; ASNA Visual RPG : Database 
Applies To: AVR for .NET 9.1.44 and up 
Article Date: 1/18/2011