Dot Net Fluke: Getting by on C# for iSeries RPG Developers

Useful tutorials on C# and .NET for RPG iSeries AS/400 developers. Brought to you from the folks at AranRock Consulting

7/7/08

When a method calls itself

This you can't do in RPG - have a method call itself. Sounds like something you would never use? Think again.

See this small console program that recurses through directories to get a list of folders and files. When the method finds a directory, it calls itself with the sub folder as a parameter whereupon the File part of the IF statement is executed.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;


namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Recurse(@"c:\temp");
}

public static void Recurse(string directory)
{
DirectoryInfo path = new DirectoryInfo(directory);
FileSystemInfo[] files = path.GetFileSystemInfos( );
foreach (FileSystemInfo file in files)
{
if (file is DirectoryInfo)
{
Console.WriteLine("Folder-> " + ((DirectoryInfo)file).FullName);


// Now the method calls itself passing in the subfolder name. When the method is called
all the files in the subfolder are listed

Recurse(((DirectoryInfo)file).FullName);
}
if (file is FileInfo)
{
Console.WriteLine("File-> " + ((FileInfo)file).FullName);
}

}



}

}
}


 



Labels: , , ,

6/17/08

C# -'The worst bloody language in the world'

My brother-in-law, a Phd wielding university prof.who has a penchant for the complex and abstruse called around yesterday to declare the whole .Net  bouquet and the languages contained therein 'the worst bloody language in the world'.  He came to me asking some very simple questions such as how to read a customer file, display it in a windows form, process it etc and was soon bogged down in connection strings, data binding, data sets etc. "But I just want to display the data and read it!" he exclaimed. He was astounded that the columns in a table aren't easily directly accessible when using SQL which isn't checked until run-time (LINQ I suggest? " Yeah but you still have to go thru hoops!"). 
Coming from procedural languages like RPG, I can sympathize. If I want to process a table in RPG  to check spending limits of a customer and update the table I do this

F CustomerFile IF   K DISK

C Read CustomerFile;
C DOW not %eof;
C If AmountSpent > CreditLimit;
C AllowSpending =False;
C Update CustomerFileR;
C Endif;


C  Read CustomerFile;
C EndDO;



The equivalent in C# is much more complicated and includes connection strings, command text and a data reader which all have to be set up.

Note that for the ONE 'F' declaration in RPG there are 3 in C#.  To even do a read needs to be setup - you need to create a data reader first. Imagine having to 'create' a read statement in RPG! The code doesn't even include the update functionality!  But the biggest problem is that column names are not typed directly in C# meaning you can't refer to the column name directly. The column 'AmountSpent' is not known to the C# program.   Linq alleviates this situation but you still have to do the setup work first.  I understand the frustration.



using System;
using IBM.Data.DB2.iSeries;

namespace iSeriesADOexample
{
class Program
{
static void Main(string[] args)
{
iDB2Connection connection =
new iDB2Connection("DataSource=PUB1.RZKH.DE; UserID=XXX; Password=xxx; DefaultCollection=COLMBYRNE1; LibraryList=COLMBYRNE1, *USRLIBL");
iDB2Command cmd = connection.CreateCommand();
connection.Open();

cmd.CommandText = "Select * from COLMBYRNE1.CUSTOMERFILE";

try
{
iDB2DataReader dataReader = cmd.ExecuteReader();
while (dataReader.Read() == true)
{

Double AmountSpent = dataReader.GetDouble(3);
Double CreditLimit = dataReader.GetDouble(4);

if (AmountSpent > CreditLimit)
{
DoUpdate();
}

}
}
catch (iDB2SQLErrorException e)
{Console.WriteLine("Error:" + e.MessageDetails);
}
Console.Read();
cmd.Dispose();
}
}

}



Programming is the art of bringing ideas to life. That's what my brother-in-law came to me for. He had an idea for a program and wanted to make it real. Unfortunately the initial  hurdle to create his simple program was far too great. The wizard functionality in Visual Studio only highlights how difficult it is to do basic actions like read a table, display the contents, process the results and update the table. This is one of the most common programs that every programmer creates yet in .NET it is difficult for a beginner.

It is clear Microsoft have 5-10 years to go before .NET is really mature and allows both beginner and seasoned developer to easily bring their ideas into the world. We program to make things better, faster, more fun, more interesting - not harder.  The tools we use then should also be better, faster - not harder. The effort to use a tool must never be greater than the effort it takes to solve the problem logically.  (Byrne's first hypothesis*) If you want to add 2 and 5 , C# should do that as easily as it does to solve it- and it does  int sum = 2+ 5;    E.g. if a client says that some of his customers are over their credit limit and needs to halt their spending then the logical solution which is to flag those spenders. The effort to implement that in C# should be that easy.  This is the benchmark which Microsoft must follow - just to keep up.

Labels: , , ,

5/5/08

LINQ to DB2 Beta available tomorrow

 

As you probably know the geeks at IBM have been scrambling to put together a LINQ to DB2 Entity framework ever since LINQ was announced - and by jove they've done it! 
You know what LINQ is right? It allows you to query data in C# and refer to columns in tables  by their column names directly just as you would any field in your C# program. Yes I know, this seems like an uber basic requirement for any language but at least it is done.
Initially LINQ had only SQL, XML and in-memory fields were supported but IBM, Oracle and MySQL quickly started getting in on the act to support their dbs.
We've learned here at Dot Net Fluke that IBM will announce tomorrow (May 6th) a beta of the LINQ to DB2 beta. We'll post the link when it comes available.
The bad news is that it is not yet available for the iSeries. So we'll all just have to sit and wait!

Update: Here's the announcement IBM Announces LINQ to DB2 connector

Labels: , , , , , ,

3/16/08

Reading an iSeries table in C# (with video)

Here's another simple example of reading a table in C# from the iSeries.
vid  See the video for this here. 

images

   Download the Visual Studio Project here

As you can see (line 13)  all you need in the connection string is the IP address of your machine. If you don't include the log on parameters -UserID=myuser; Password=mypass, then you will be prompted for them by the iSeries.
You need to include the IBM .net provider in your 'References' in your Visual Studio project. It's located in the Client Access directory. If you don't have client access, then download the 'technology preview' from IBM for free.

   1:  using System;


   2:  using System.Collections.Generic;


   3:  using System.Text;


   4:  using IBM.Data.DB2.iSeries;


   5:   


   6:  namespace Prez_Rank


   7:  {


   8:      class Program


   9:      {


  10:          static void Main(string[] args)


  11:          {


  12:              iDB2Connection conn = new iDB2Connection();


  13:              conn.ConnectionString = "DataSource=192.168.0.1";


  14:              conn.Open();


  15:   


  16:              iDB2Command cmd = new iDB2Command();


  17:              cmd.CommandText = "Select * from colm.customers";


  18:              cmd.Connection = conn;


  19:   


  20:   


  21:   


  22:              iDB2DataReader dr= cmd.ExecuteReader();


  23:   


  24:              while (dr.Read())


  25:              {


  26:                  Console.WriteLine(dr.GetString(0));


  27:   


  28:              }


  29:              Console.ReadLine();


  30:   


  31:          }


  32:      }


  33:  }


Labels: , , , ,

11/15/07

Calling a program on an iSeries with .net

Here's an easy way to call a program on the AS/400 with .net. What's neat about this function is that it also accepts a return argument.

Requirements:
  1. Client Access
    This method uses the Client Access library so you will need to have Client Access installed on your PC. Specifically you need cwbx.dll which is in the Iseries access folder C:\Program Files\IBM\Client Access\Shared
    Add this into your Visual Studio project as a reference
  2. The host server on the iSeries must be started using STRHOSTSVR SERVER(*ALL)
    Copy the code below into Visual Studio



    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO;
    using cwbx; // cwbx.dll is in the Iseries access folder C:\Program Files\IBM\Client Access\Shared
    // YOu must add it in to you project; Click on your references folder in solution explorer to add it in.

    namespace CallAS400pgm
    {
    class Program
    {


    static void Main(string[] args)
    {

    // Modify the following for your own iSeries

    string AS400Name = "192.168.0.1"; // Change this to the IP address of your machine
    string AS400User = "USER"; // User name to sign on to AS/400
    string AS400Password = "password"; // Password used to sign on to the AS/400
    string AS400Pgm = "DOTNET1"; //Name of program you wish to call on the AS/400
    string AS400Lib = "QGPL"; // Name of library where the program is located


    Console.WriteLine("Creating AS/400 object....");

    cwbx.AS400System AS400 = new cwbx.AS400SystemClass(); // creates an as/400 object
    cwbx.Program program = new cwbx.Program(); // Create a program object

    AS400.Define(AS400Name); // IP of AS/400

    program.system = AS400;
    program.system.UserID = AS400User; // Your user name
    program.system.Password = AS400Password; // Your password

    // define the name of the program you want to call on the iSeries
    program.LibraryName = AS400Lib; //Library where your program is located
    program.ProgramName = AS400Pgm; // Program that this app will call

    // NOTE: before you sign on, the host server on the iSeries must be started using STRHOSTSVR SERVER(*ALL)
    Console.WriteLine("Signing on to " + AS400Name);
    AS400.Signon();
    AS400.Connect(cwbcoServiceEnum.cwbcoServiceRemoteCmd);

    if (AS400.IsConnected(cwbcoServiceEnum.cwbcoServiceAll) == 0)
    {
    Console.WriteLine("Not connected");
    }
    else
    {
    ProgramParameters parms = new ProgramParameters(); // must create parameter collection
    // parms.Clear(); // if you have no parm use this statement

    // Define the parms you are sending and receiving from the iSeries pgm
    parms.Append("MsgToAS400", cwbrcParameterTypeEnum.cwbrcInput, 30); // // Input parm called 'MsgToAS400;
    parms.Append("ReplyFromAS400", cwbrcParameterTypeEnum.cwbrcOutput, 30); // create a parameter object name, type & length

    // puts a value into the parameter object
    StringConverter strcon = new StringConverterClass();
    strcon.Length = 30;
    parms["MsgToAS400"].Value = strcon.ToBytes(" This is from a dot net pgm, hi");


    try
    {
    Console.WriteLine("Sending a message to the iSeries....");

    Console.WriteLine("Calling program on the AS400....");
    program.Call(parms); // Runs until job is completed

    // Get the return value from the ISeries pgm

    String reply = strcon.FromBytes(parms["ReplyFromAS400"].Value);
    Console.WriteLine(reply);
    //This program dotnet1 is called on the iSeries. dotnet1 has 2 parameters
    // it takes the first and displays it to a user
    // it then sends back a message to this class in ReplyFromAS400

    Console.ReadLine();


    }
    catch (Exception e)
    {
    foreach (Error error in AS400.Errors)
    {
    Console.WriteLine(error.ToString());
    }

    throw;
    }

    }


    }
    }
    }


Labels: , , , , , , , ,