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.
1 2 3 4 5 6 |
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. TheName
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 theExamples/CMastNewL2
file had a field namedCMCustNo
you’d need to reference in it as in your code asCust_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 theDclDiskFile
and theDclMemoryFile
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
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.
1 2 3 4 5 6 7 |
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 how to write to a MemoryFile. Sometimes, though, rather than dealing with AVR’s higher level ways to write to the MemoryFile, you also want to work at a lower level with the MemoryFile’s underlying DataGate. The routines ListColumnNamesAndType
and ListCoumnValue
show how to read a DataGate’s data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
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. TheName
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 theExamples/CMastNewL2
file had a field namedCMCustNo
you’d need to reference in it as in your code asCust_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 theDclDiskFile
and theDclMemoryFile
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.