C# - Insert Multiple Records at once to AS400 - asp.net

I have a problem like this:
1. I retrieve data from MySQL using C# ASP .Net. -- done --
2. All data from no.1 will be inserted into table on AS400. -- I got an error on this step --
Error message says that ERROR [42000] [IBM][System i Access ODBC Driver][DB2 for i5/OS]SQL0104 - Token ; was not valid. Valid tokens: <END-OF-STATEMENT>.. It's true that I used semicolon to separate queries with each others, but it's not allowed. I've Googling but I can't find the solution.
My question is what the <END-OF-STATEMENT> means of that error message..?
Here is my source code.
private static void doInsertDOCADM(MySqlConnection conn)
{
// Get Temporary table
String query = "SELECT * FROM TB_T_DOC_TEMPORARY_ADM";
DataTable dt = CSTDDBUtil.ExecuteQuery(query);
OdbcConnection as400Con = null;
as400Con = CSTDDBUtil.GetAS400Connection();
as400Con.Open();
if (dt != null && dt.Rows.Count > 0)
{
int counter = 1, maxInsertLoop = 50;
using (OdbcCommand cmd = new OdbcCommand())
{
cmd.Connection = as400Con;
foreach (DataRow dr in dt.Rows)
{
cmd.CommandText += "INSERT INTO DCDLIB.WDFDOCQ VALUES " + "(?,?,?,?);";
cmd.Parameters.Add("1", OdbcType.VarChar).Value = dr["PROD_MONTH"].ToString();
cmd.Parameters.Add("2", OdbcType.VarChar).Value = dr["NEW_MAIN_DEALER_CD"].ToString();
cmd.Parameters.Add("3", OdbcType.VarChar).Value = dr["MODEL_SERIES"].ToString();
cmd.Parameters.Add("4", OdbcType.VarChar).Value = dr["MODEL_CD"].ToString();
if (counter < maxInsertLoop)
{
counter++;
}
else
{
counter = 1;
cmd.ExecuteNonQuery();
cmd.CommandText = "";
cmd.Parameters.Clear();
}
}
if (counter > 1) cmd.ExecuteNonQuery();
}
}
Notes: I used this way (Collect some queries first, and then execute those query) to improve the performance of my application.

As Clockwork-Muse pointed out, the problem is that you can only run a single SQL statement in a command. The iSeries server does not handle multiple statements at once.
If your iSeries server is running V6R1 or later, you can use block inserts to insert multiple rows. I'm not sure if you can do so through the ODBC driver, but since you have Client Access, you should be able to install the iSeries ADO.NET driver. There are not many differences between the ADO.NET iSeries driver and the ODBC one, but with ADO.NET you get access to iSeries specific functions.
With the ADO.NET driver, multiple insert become a simple matter of :
using (iDB2Connection connection = new iDB2Connection(".... connection string ..."))
{
// Create a new SQL command
iDB2Command command =
new iDB2Command("INSERT INTO MYLIB.MYTABLE VALUES(#COL_1, #COL_2", connection);
// Initialize the parameters collection
command.DeriveParameters();
// Insert 10 rows of data at once
for (int i = 0; i < 20; i++)
{
// Here, you set your parameters for a single row
command.Parameters["#COL_1"].Value = i;
command.Parameters["#COL_2"].Value = i + 1;
// AddBatch() tells the command you're done preparing a row
command.AddBatch();
}
// The query gets executed
command.ExecuteNonQuery();
}
}
There is also some reference code provided by IBM to do block inserts using VB6 and ODBC, but I'm not sure it can be easily ported to .NET : http://publib.boulder.ibm.com/infocenter/iseries/v5r4/index.jsp?topic=%2Frzaik%2Frzaikextfetch.htm
Hope that helps.

When it says <END-OF-STATEMENT> it means about what it says - it wants that to be the end of the executed statement. I don't recall if the AS/400 allows multiple statements per execution unit (at all), but clearly it's not working here. And the driver isn't dealing with it either.
Actually, you have a larger, more fundamental problem; specifically, you're INSERTing a row at a time (usually known as row-by-agonizing-row). DB2 allows a comma-separated list of rows in a VALUES clause (so, INSERT INTO <table_name> VALUES(<row_1_columns>), (<row_2_columns>)) - does the driver you're using allow you to provide arrays (either of the entire row, or per-column)? Otherwise, look into using extract/load utilities for stuff like this - I can guarantee you that this will speed up the process.

Related

.Net Core Npgsql Return Single Row

I was just wondering if it's possible to return a single row using Npgsql in a .Net Core Script.
I've been doing with the Read Method like bellow
NpgsqlCommand command = new NpgsqlCommand (
" select col01, col02, col03 from table ",
dbconnection
);
var result = command.ExecuteReader();
while(result.Read()) {
var varCol01 = result["col01"];
var varCol02 = result["col02"];
}
Which seems a bit excessive for a single row because I would have to exit the while manually.
Each time you call NpgsqlDataReader.Read, you're reading an additional row - so your code sample doesn't seem to be a good fit for a single row scenario...
If you know you're only getting a single row, why not do something along the lines of:
var reader = command.ExecuteReader();
reader.Read(); // You can do a Debug.Assert to make sure the result of this is true
var (col1, col2) = (result["col01"], result["col02"]);

SQLite via JDBC: SQLITE_BUSY when inserting after selecting

I'm getting the error code SQLITE_BUSY when trying to write to a table after selecting from it. The select statement and result is properly closed prior to my insert.
If I'm removing the select part the insert works fine. And this is what I'm not getting. According to the documentation SQLITE_BUSY should mean that a different process or connection (which is definetly not the case here) is blocking the database.
There's no SQLite manager running. Also jdbcConn is the only connection to the database I have. No parallel running threads aswell.
Here's my code:
try {
if(!jdbcConn.isClosed()) {
ArrayList<String> variablesToAdd = new ArrayList<String>();
String sql = "SELECT * FROM VARIABLES WHERE Name = ?";
try (PreparedStatement stmt = jdbcConn.prepareStatement(sql)) {
for(InVariable variable : this.variables.values()) {
stmt.setString(1, variable.getName());
try(ResultSet rs = stmt.executeQuery()) {
if(!rs.next()) {
variablesToAdd.add(variable.getName());
}
}
}
}
if(variablesToAdd.size() > 0) {
String sqlInsert = "INSERT INTO VARIABLES(Name, Var_Value) VALUES(?, '')";
try(PreparedStatement stmtInsert = jdbcConn.prepareStatement(sqlInsert)) {
for(String name : variablesToAdd) {
stmtInsert.setString(1, name);
int affectedRows = stmtInsert.executeUpdate();
if(affectedRows == 0) {
LogManager.getLogger().error("Error while trying to add missing database variable '" + name + "'.");
}
}
}
jdbcConn.commit();
}
}
}
catch(Exception e) {
LogManager.getLogger().error("Error creating potentially missing database variables.", e);
}
This crashes on int affectedRows = stmtInsert.executeUpdate();. Now if I remove the first block (and manually add a value to the variablesToAdd list) the value inserts fine into the database.
Am I missing something? Am I not closing the ResultSet and PreparedStatement properly? Maybe I'm blind to my mistake from looking at it for too long.
Edit: Also executing the select in a separate thread does the trick. But that can't be the solution. Am I trying to insert into the database too fast after closing previous statements?
Edit2: I came across a busy_timeout, which promised to make updates/queries wait for a specified amount of time before returning with SQLITE_BUSY. I tried setting the busy timeout like so:
if(jdbcConn.prepareStatement("PRAGMA busy_timeout = 30000").execute()) {
jdbcConn.commit();
}
The executeUpdate() function still immedeiately returns with SQLITE_BUSY.
I'm dumb.
I was so thrown off by the fact that removing the select statement worked (still not sure why that worked, probably bad timing) that I missed a different thread using the same file.
Made both threads use the same java.sql.Connection and everything works fine now.
Thank you for pushing me in the right direction #GordThompson. Wasn't aware of the jdbc:sqlite::memory: option which led to me finding the issue.

Creating an in-memory Database and Table if not exist

I am trying to create an in-memory DB using sqllite
Connection inMemConn = null;
Class.forName("org.sqlite.JDBC");
inMemConn = DriverManager.getConnection("jdbc:sqlite:inMemDB.db");
Statement DBCreateTable = inMemConn.createStatement();
DBCreateTable.executeUpdate("CREATE DATABASE IF NOT EXISTS inMemDB" );
DBCreateTable.executeUpdate("USE inMemDB");
DBCreateTable. executeUpdate("CREATE TABLE IF NOT EXISTS pNumbertable");
and i am trying to populate it with some Java Objects
final String DBinsert = "INSERT INTO pNumbertable VALUES(?,?)";
PreparedStatement DBAddObjects = inMemConn.prepareStatement(DBinsert);
for (int i = 0; i < List.size(); i++) {
for (int j = 0; j < List2.size(); j++) {
DBAddObjects.setObject(1, XList.get(i)[0]);
DBAddObjects.setObject(2, YList.get(j)[1]);
DBAddObjects.executeUpdate();
}
}
DBAddObjects.close();
inMemConn.close();
But, while running the code, I get the following error.
near "DATABASE": syntax error
Also, is it the correct way to create an in-mem DB.?
There is on CREATE DATABASE statement in SQLite; databases are created by creating a connection with the desired file name.
You have opened (or created) a database with the file name inMemDB.db.
To create an in-memory database, you must use the special file name :memory: instead:
DriverManager.getConnection("jdbc:sqlite::memory:");

Retrieve Cellset Value in SSAS\MDX

Im writing SSAS MDX queries involving more than 2 axis' to retrieve a value. Using ADOMD.NET, I can get the returned cellset and determine the value by using
lblTotalGrossSales.Text = CellSet.Cells(0).Value
Is there a way I can get the CellSet's Cell(0) Value in my MDX query, instead of relying on the data returning to ADOMD.NET?
thanks!
Edit 1: - Based on Daryl's comment, here's some elaboration on what Im doing. My current query is using several axis', which is:
SELECT {[Term Date].[Date Calcs].[MTD]} ON 0,
{[Sale Date].[YQMD].[DAY].&[20121115]} ON 1,
{[Customer].[ID].[All].[A612Q4-35]} ON 2,
{[Measures].[Loss]} ON 3
FROM OUR_CUBE
If I run that query in Management Studio, I am told Results cannot be displayed for cellsets with more than two axes - which makes sense since.. you know.. there's more than 2 axes. However, if I use ADOMD.NET to run this query in-line, and read the returning value into an ADOMD.NET cellset, I can check the value at cell "0", giving me my value... which as I understand it (im a total noob at cubes) is the value sitting where all these values intersect.
So to answer your question Daryl, what I'd love to have is the ability to have the value here returned to me, not have to read in a cell set into the calling application. Why you may ask? Well.. ultimately I'd love to have one query that performs several multi-axis queries to return the values. Again.. Im VERY new to cubes and MDX, so it's possible Im going at this all wrong (Im a .NET developer by trade).
Simplify your query to return two axis;
SELECT {[Measures].[Loss]} ON 0, {[Term Date].[Date Calcs].[MTD] * [Sale Date].[YQMD].[DAY].&[20121115] * [Customer].[ID].[All].[A612Q4-35]} ON 1 FROM OUR_CUBE
and then try the following to access the cellset;
string connectionString = "Data Source=localhost;Catalog=AdventureWorksDW2012";
//Create a new string builder to store the results
System.Text.StringBuilder result = new System.Text.StringBuilder();
AdomdConnection conn = new AdomdConnection(connectionString);
//Connect to the local serverusing (AdomdConnection conn = new AdomdConnection("Data Source=localhost;"))
{
conn.Open();
//Create a command, using this connection
AdomdCommand cmd = conn.CreateCommand();
cmd.CommandText = #"SELECT { [Measures].[Unit Price] } ON COLUMNS , {[Product].[Color].[Color].MEMBERS-[Product].[Color].[]} * [Product].[Model Name].[Model Name]ON ROWS FROM [Adventure Works] ;";
//Execute the query, returning a cellset
CellSet cs = cmd.ExecuteCellSet();
//Output the column captions from the first axis//Note that this procedure assumes a single member exists per column.
result.Append("\t\t\t");
TupleCollection tuplesOnColumns = cs.Axes[0].Set.Tuples;
foreach (Microsoft.AnalysisServices.AdomdClient.Tuple column in tuplesOnColumns)
{
result.Append(column.Members[0].Caption + "\t");
}
result.AppendLine();
//Output the row captions from the second axis and cell data//Note that this procedure assumes a two-dimensional cellset
TupleCollection tuplesOnRows = cs.Axes[1].Set.Tuples;
for (int row = 0; row < tuplesOnRows.Count; row++)
{
for (int members = 0; members < tuplesOnRows[row].Members.Count; members++ )
{
result.Append(tuplesOnRows[row].Members[members].Caption + "\t");
}
for (int col = 0; col < tuplesOnColumns.Count; col++)
{
result.Append(cs.Cells[col, row].FormattedValue + "\t");
}
result.AppendLine();
}
conn.Close();
TextBox1.Text = result.ToString();
} // using connection
Source : Retrieving Data Using the CellSet
This is fine upto select on columns and on Rows. It will be helpful analyze how to traverse sub select queries from main query.

SQL always returning same results

I'm trying to do a search on a table in my database where it returns the top 50 rows with a firstname like the search term being passed to the function, but its always returning the same 50 results
My sql looks like this:
Select TOP(50) *
FROM [database].[dbo].[records]
WHERE (A_1STNAME LIKE '" + #searchTerm + "%')
ORDER BY A_RECID
When I run this query in Visual Studios query window, it works as expected but when I run it through my ASP.NET application, it always returns the same 50 results, and only one of them has a first name close to the searchTerm I passed to it.
here is the page code that runs the function:
protected void Page_Load(object sender, EventArgs e)
{
_migrated_data data = new _migrated_data();
DataSet ds = data.Search(Request.QueryString.Get("query"), "A_RECID", 50);
if (ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
{
rpt_Data.DataSource = ds.Tables[0].DefaultView;
rpt_Data.DataBind();
}
}
and here is the search method of _migrated_data:
public DataSet Search(String #pSearchTerm, String #pSortBy, int #pRowCount)
{
DataSet ds = new DataSet();
OleDbConnection objOleDBConn;
OleDbDataAdapter objOleDBDa;
objOleDBConn = new OleDbConnection(ClearingHouse_OLEDDB);
objOleDBConn.Open();
string lSQL = "SELECT TOP(50) * FROM [database].[dbo].[records]";
lSQL += " WHERE (A_1STNAME LIKE #searchTerm ) ORDER BY #sortBy";
SqlCommand t = new SqlCommand(lSQL);
if (pSearchTerm != null && pSearchTerm != "")
{
t.Parameters.AddWithValue("#searchTerm", #pSearchTerm + "%");
}
if (pSortBy != null && pSortBy != "")
{
t.Parameters.AddWithValue("#sortBy", #pSortBy);
}
else
{
t.Parameters.AddWithValue("#sortBy", "A_RECID");
}
objOleDBDa = new OleDbDataAdapter(t.CommandText, objOleDBConn);
objOleDBDa.SelectCommand.CommandType = CommandType.Text;
objOleDBDa.Fill(ds);
objOleDBConn.Close();
return ds;
}
Using locals to view the final CommandText of t, I get the sql results I gave above.
Any help is greatly appriciated :)
AS Rob Rodi said, first of all, to get the results begining with a value you will need % char at the end of the therm:
Select TOP(50) *
FROM [database].[dbo].[records]
WHERE (A_1STNAME LIKE '" + #searchTerm + "%')
ORDER BY A_RECID
Second, probably your searchTerm is empty, so it's grabbing always the same results.
Debug your app and watch the variable to see if it's receiving some value.
You are having SQL injection there. Also it looks like the %-signs are missing.
The fact, that the query works in a query window and not in your app means, that the error is not in your query! Did you think about that? Probably, you should post code of your ASP.NET app.
It sounds like your parameter isn't being correctly passed to your data layer. Easiest way to find out if that's the case is to turn on Sql Profiler and check to see if it's being passed. If it is, your sql's to blame. If it's not, it's your application. You can then use the debugger on your application to work your way up the stack to see where the problem lies.

Resources