The MemoryFile is one of ASNA Visual RPG's (AVR) most frequently used objects. The MemoryFile is a programming interface that wraps .NET's System.Data.DataSet. It allows rows to be written to an underlying DataTable with the RPG Write operation. The MemoryFile's resulting DataSet and single DataTable are often used to bind data to UI interface elements such as GridViews and DataGridViews.

There are two ways to describe a MemoryFile in AVR:

  • Externally described
  • Program described

Externally Described MemoryFiles

Externally described MemoryFiles have a field layout (the table schema) that is defined by an existing physical or logical file. 

DclMemoryFile CustMem                   +
        DBDesc("*Public/DG Net Local")  +
        Prefix(Cust_)                   +
        FileDesc("Examples/CMastNewL2") +
        RnmFmt(CustMemR)     

Figure 1a. Externally described memory file

Figure 1a above shows how to externally describe a memory file. In this case, the memory file has 100% the same fields as the field defined in the FileDesc keyword (in this case, Examples/CMastNewL2). At compile time, AVR copies the specified file's description to the MemoryFile. Figure 1a shows a typical use of the MemoryFile. 

  • CustMem provides the name for the memory file. The Name keyword is optional. For clarity, the other keywords are specified in this example.
  • The DBDesc keyword is required for externally described MemoryFiles. This identifies the DataGate database connection where the specified file is located.
  • The Prefix keyword is optional. If you use it, you must always refer to the fields in the MemoryFile using the prefix. Assuming the Examples/CMastNewL2 file had a field named CMCustNo you'd need to reference in it as in your code as Cust_CMCustNo. As a general rule, if you want to read from a physical or logical file and write to a MemoryFile without explicitly assigning field values, be sure both the DclDiskFile and the DclMemoryFile have consistent prefixes defined. 
  • The FileDesc keyword is required for externally described MemoryFiles. It identifies the library and file used to externally describe the MemoryFile.
  • The RnmFmt key is optional. Many times, though, the file used to define a MemoryFile is also used as a DclDiskFile. Therefore, to avoid a record format name collision, you'll quite often need to use the RnmFmt with externally described MemoryFiles.

MemoryFiles can be written to like a physical or logical file and have Open() and Close() methods. However, MemoryFiles are not really files (ie, they don't have a handle that ties them to a underlying database platform) and don't need to be explicitly opened or closed. The ImpOpen keyword can be used to require the use of Open() and Close() methods with a MemoryFile but its use is superfluous and just adds noise to your code. Best practice: don't include the ImpOpen keyword with MemoryFiles and don't worry about opening or closing them. They are "open" by default (which for a MemoryFile means they are instanced by default) and "closed" (ie, reclaimed by the garbage collector) when they go out of scope in your program. 

One of the reasons to use an externally described MemoryFile is when you need to store the contents of one or more records read from a physical or logical file. The fragment below shows this is in action:

DclDB pgmDB DBName("*Public/DG NET Local")  

DclDiskFile  Cust                   +
        Type(*Input )               +
        Org(*Indexed)               +
        Prefix(Cust_)               +
        File("Examples/CMastNewL2") +
        DB(pgmDB)                   +
        ImpOpen(*No)          

DclMemoryFile CustMem                   +
        DBDesc("*Public/DG Net Local")  +
        Prefix(Cust_)                   +
        FileDesc("Examples/CMastNewL2") +
        RnmFmt(CustMemR)     

BegSr TestWrite
    Connect pgmDB  
    Open Cust
    // Ensure CustMem has no rows.
    CustMem.ClearFileData()
    Console.WriteLine("DataSet rows: {0}", CustMem.DataSet.Tables[0].Rows.Count)
     
    // Read from the data file.
    Read Cust 

    // Write it to the MemoryFile (note the prefixes must be the same!)
    Write CustMem
    Console.WriteLine("DataSet rows: {0}", CustMem.DataSet.Tables[0].Rows.Count)
    
    CustMem.ClearFileData()
    Console.WriteLine("DataSet rows: {0}", CustMem.DataSet.Tables[0].Rows.Count)

    Close Cust
    Disconnect pgmDB
EndSr

Figure 1b. A typical use of an externally described MemoryFile.

Program Described MemoryFiles

A program described MemoryFile has its record format layout described inline--in the program. While "program described" anything in a traditional RPG program is often looked down on, program described MemoryFiles in AVR are very popular (you'll probably find yourself using more of them than using externally described MemoryFiles) and more closely persist the traditional RPG program-described subfile idiom.

DclMemoryFile CustMem
    DclRecordFormat Customers

    DclRecordFld    Cust_CMCustNo Type(*Packed) Len(9,0)
    DclRecordFld    Cust_CMName   Type(*Char) Len(40)
    DclRecordFld    Cust_Email    Type(*Char) Len(40)

Figure 2a. Program described memory file

Figure 2a above shows how to program describe a MemoryFile. In this case, the memory file has three fields. The name and type for each field must be explicitly provided. With an externally described MemoryFile, it's quite challenging to add a field to the MemoryFile schema at runtime. However, with the program described technique shown above, it's easy to create an arbitrary schema. For nearly all tasks related to populating UI elements with a MemoryFile, you'll want to use a program described MemoryFile. Its superior flexibility over the externally described memory file make the extra typing required worthwhile.

With a program described MemoryFile, at compile time AVR copies the specified file's description to the MemoryFile. Figure 2b shows a canonical use of the MemoryFile. 

Using System
Using System.Collections
Using System.Text
Using System.Data

BegClass Program

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

        DclSrParm args Type(*String) Rank(1)            

        (*New Test()).Run()      

        Console.WriteLine("Press any key to continue...")
        Console.ReadKey()
    EndSr

EndClass

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

    DclDiskFile  Cust                   +
          Type(*Input )               +
          Org(*Indexed)               +
          Prefix(Cust_)               +
          File("Examples/CMastNewL2") +
          DB(pgmDB)                   +
          ImpOpen(*No)          

    DclMemoryFile CustMem                   +
          DBDesc("*Public/DG Net Local")  +
          Prefix(Cust_)                   +
          FileDesc("Examples/CMastNewL2") +
          RnmFmt(CustMemR)

    BegSr Run Access(*Public)
        DclFld Message Type(*String)

        Message = "There are {0} rows in CustMem's zeroth DataTable"
        Connect pgmDB
        Open Cust

        Do FromVal(1) ToVal(10)
            Read Cust
            Write CustMem       
        EndDo

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

        ListColumnNamesAndType()
        ListColumnValue()

        Close Cust
        Disconnect pgmDB
    EndSr   

    BegSr ListColumnNamesAndType
        DclFld dt Type(DataTable)

        dt = CustMem.DataSet.Tables[ 0 ]
        ForEach Col Type(DataColumn) Collection(dt.Columns)
            Console.WriteLine(Col.ColumnName + " " + Col.DataType)     
        EndFor   
    EndSr

    BegSr ListColumnValue
        DclFld dt Type(DataTable)

        dt = CustMem.DataSet.Tables[ 0 ]
        ForEach Row Type(DataRow) Collection(dt.Rows)
            Console.WriteLine(Row[ "CMCustNo" ].ToString() ++
                " " + Row[ "CMName" ].ToString())      
        EndFor       
    EndSr  

EndClass

Figure 2b. Example program writing to a MemoryFile

The example above uses these keywords with the MemoryFile:

  • CustMem provides the name for the memory file. The Name keyword is optional. For clarity, the other keywords are specified in this example.
  • The DBDesc keyword is required for externally described MemoryFiles. This identifies the DataGate database connection where the specified file is located.
  • The Prefix keyword is optional. If you use it, you must always refer to the fields in the MemoryFile using the prefix. Assuming the Examples/CMastNewL2 file had a field named CMCustNo you'd need to reference in it as in your code as Cust_CMCustNo. As a general rule, if you want to read from a physical or logical file and write to a MemoryFile without explicitly assigning field values, be sure both the DclDiskFile and the DclMemoryFile have consistent prefixes defined. 
  • The FileDesc keyword is required for externally described MemoryFiles. It identifies the library and file used to externally describe the MemoryFile.
  • The RnmFmt key is optional. Many times, though, the file used to define a MemoryFile is also used as a DclDiskFile. Therefore, to avoid a record format name collision, you'll quite often need to use the RnmFmt with externally described MemoryFiles. 

The DataTable can have 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:

A quick note about Figure 2b: Figure 2b uses a Console application project type. If you haven’t yet used a console application, consider 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:

MemoryFile Rules of Thumb

  • Use externally described MemoryFiles when you need a program buffer that exactly echoes the layout of a physical or logical file. This might be the case when you want to save the contents of a record buffer to implement optimistic record locking or implement a record caching pattern.  It is challenging to add additional columns to an externally described MemoryFile at runtime--and impossible to access dyamically added columns with RPG operation codes (because the RPG operations only know about the columns that were present then the MemoryFile was instanced). 
  • Use program described MemoryFiles when you need to describe a specific schema for a buffer of data. For example, you may want to populate a list with three columns from a specific record format but also add to sum those columns values. 
  • Remember that MemoryFiles are just that--files in memory. Don't get carried away writing thousands of rows to a memory file. Put too many rows in a MemoryFile and at some point you'll make smoke come out of the computer! 
  • The MemoryFile object wraps the System.Data.DataSet object. Therefore, once you've populated a MemoryFile, its DataSet property can be used and modified with the same techniques you'd use on a DataSet populated in any other fashion.