PetaPoco not returning expected data - c#

I have a BIT data type on one of my columns.
I have written a query that does SELECT * FROM TABLE WHERE BITCOLUMN <> #0
It works fine if I pass in 1 or 0 but if I pass in 3 PetaPoco doesn't return the results I expect.
Executing the SQL in a query window does return all records when I use 3 as the parameter value.
Any ideas?
UPDATE: If I use string SQL = "SELECT * FROM TABLE WHERE BITCOLUMN <> " + MethodParam; This returns data as expected.

Could you tell me what result you expecting? According to MSDN, A bit column can either be 1, 0 or null. It does not make senses to me when you pass 3 to it, since it will select all rows.
And my quick test shows that Petapoco behaves as expected.
using (var database = new Database("sql"))
{
string sql = "SELECT COUNT(*) FROM TBLTESTBIT WHERE BITCOLUMN <> " + "3";
var test = database.ExecuteScalar<long>("SELECT COUNT(*) FROM TBLTESTBIT WHERE BITCOLUMN <> #0", 3);
var test2 = database.ExecuteScalar<long>(sql);
Console.WriteLine(test == test2); // this output true
Console.Read();
}

I think it will be because when you use a parametrised sql string, you are passing in int with a value of 3 in as one of the args. PetaPoco will then create an IDataParameter for that argument and the DbType will be set to DbType.Int32 by default since PetaPoco has no idea what the underlying table column type is.

Use new Sql() not Append.
You should use Query = new PetaPoco.Sql( over Query = PetaPoco.Sql.Append

Related

Why this SQL Server query can't work when I put it into a C# method?

I have the following problem trying to insert the following SQL server query into a C# method:
select *
from VulnerabilityReference
where Title = 'Message'
and convert(nvarchar(MAX), Description2) = N'http://www.securityfocus.com/archive/1/1201040152.5924.44.camel#laptop'
As you can see the Description2 field is casted
In Microsoft SQL Server Managment Studio the previous query work well and give me the following output:
Id Title Description2
52794 Message http://www.securityfocus.com/archive/1/1201040152.5924.44.camel#laptop
55340 Message http://www.securityfocus.com/archive/1/1201040152.5924.44.camel#laptop
55341 Message http://www.securityfocus.com/archive/1/1201040152.5924.44.camel#laptop
55342 Message http://www.securityfocus.com/archive/1/1201040152.5924.44.camel#laptop
Now I creat the following exist() method in C#
public long existReference(DataModel.Vulnerability.Reference reference)
{
long id = -1;
_strSQL = "SELECT * FROM VulnerabilityReference"
+ " WHERE Title = #REFERENCETITLE and convert(nvarchar(MAX), Description2) = N'#DESCRIPTION2' ";
System.Data.Common.DbCommand command;
command = _connection.CreateCommand();
_addParameter(command, "#REFERENCETITLE", reference.Title);
_addParameter(command, "#DESCRIPTION2", reference.Description2);
command.CommandText = _strSQL;
_dt = _fillDataTable(command);
if (_dt.Rows.Count == 0)
{
return -1;
}
id = _dt.Rows[0].Field<int>("Id");
return id;
}
The two field of the reference object contains the same values of the previous example but it don't work fine and found 0 records for this query.
Why? What could be the problem? How can I solve it?
Tnx
N'#DESCRIPTION2' is looking for the literal text #DESCRIPTION2 (not the value of the parameter by that name). If you want the parameter value, just use #DESCRIPTION2:
WHERE ...(blah)... = #DESCRIPTION2
Drop all things around variables that tell the parser what type of variable it is. That's already known:
N'#DESCRIPTION2'
in your statement needs to be
#DESCRIPTION2
your query should look like
_strSQL = "SELECT * FROM VulnerabilityReference"
+ " WHERE Title = #REFERENCETITLE and convert(nvarchar(MAX), Description2) = #DESCRIPTION2 ";

How does one do an Oracle top-n (paged) query in SubSonic 2.2?

(Disclaimer: I changed/obfuscated some of the variable/table/column names here for security reasons. Please forgive me if something looks a little off.)
I am building a front-end to an Oracle 10g database, and I'm trying to get paged data. Aside from paging, the following SubSonic 2.2 code gives me what I want, in the order I want it:
var q = new Select()
.From(AppDb.MyTable.Schema)
.Where(AppDb.MyTable.DisabledDateColumn).IsNull()
.OrderDesc(AppDb.MyTable.CreatedDateColumn.ColumnName)
System.Console.Out.Writeline(q.BuildStatement());
This yields the following SQL:
SELECT
MYSCHEMA.MYTABLE.ID,
MYSCHEMA.MYTABLE.DISABLED_DATE
FROM
MYSCHEMA.MYTABLE
WHERE
MYSCHEMA.MYTABLE.DISABLED_DATE IS NULL
ORDER BY
CREATED_DATE DESC
Then I try to introduce paging:
var q = new Select()
.From(AppDb.MyTable.Schema)
.Where(AppDb.MyTable.DisabledDateColumn).IsNull()
.OrderDesc(AppDb.MyTable.CreatedDateColumn.ColumnName)
.Paged(0, 10);
And it wipes out my WHERE and ORDER BY clauses:
SELECT * FROM (
SELECT
MYSCHEMA.MYTABLE.ID,
MYSCHEMA.MYTABLE.DISABLED_DATE,
ROWNUM as row_number
FROM
MYSCHEMA.MYTABLE
)
WHERE
row_number BETWEEN 1 AND 10
I'm brand new to SubSonic, so I don't know if that's a bug, or I'm just not doing it the preferred way, or if Oracle demands that this be done in a different, more Oracle-centric way. (Oracle seems to demand that of everything.)
What I want, in case it isn't obvious, is each succeeding page to include the 10 next most-recently-created, non-disabled records. How can I do this in SubSonic 2.2?
It looks like your query is not generating the same SQL as the current Oracle Data Provider for SubSonic should. Here is the relevant code (starting at line 852):
if(qry.PageIndex < 0)
query = String.Format("{0} {1} FROM {2}.{3} {4} {5}",select ,columns, table.SchemaName, table.Name, where, order);
else
{
int start = qry.PageIndex * qry.PageSize;
int end = (qry.PageIndex + 1) * qry.PageSize;
const string cteFormat =
"WITH pagedtable AS (SELECT {0}, ROW_NUMBER () OVER ({1}) AS rowindex FROM {2}.{3} {4}) SELECT {5}, rowindex FROM pagedtable WHERE rowindex >= {6} AND rowindex < {7} ORDER BY rowindex";
query = string.Format(cteFormat, columns, order,table.SchemaName, table.Name, where, columns.Replace(table.Name + ".", String.Empty), start, end);
}
return query;
Perhaps an update to current source would do the trick. If there is something wrong with the actual provider, you can compare it to the SqlDataProvider for hints as to what might be the problem.
I'm in an environment using NET 2.0 and Oracle 9i R2 and I ran into the same exact problem. I'm using SubSonic 2.2.
I downloaded the source from GitHub and this is what I found:
The code quoted by #ranomore (OracleDataProvider.GetSelectSql()) is only called when using the SubSonic.Query object. Since the OP and myself use the Select object which is derived from the newer and more powerful SubSonic.SqlQuery object, OracleDataProvider.GetSelectSql() never gets called. Instead, OracleGenerator.BuildPagedSelectStatement() gets called and generates the SQL you see posted by the OP. This code is buggy as it never adds the WHERE and ORDER BY clauses to the pagination query it ultimately generates.
I replaced the contents of BuildPagedSelectStatement() with something based on ANSISqlGenerator.BuildSelectStatement():
public override string BuildPagedSelectStatement()
{
int startnum = query.PageSize * query.CurrentPage + 1;
int endnum = query.PageSize * query.CurrentPage + query.PageSize;
string orderBy = String.Empty;
if (this.query.OrderBys.Count > 0)
orderBy = GenerateOrderBy();
//The ROW_NUMBER() function in Oracle requires an ORDER BY clause.
//In case one is not specified, we need to halt and inform the caller.
if(orderBy.Equals(String.Empty))
throw new ArgumentException("There is no column specified for the ORDER BY clause", "OrderBys");
System.Text.StringBuilder sql = new System.Text.StringBuilder();
//Build the command string
sql.Append("WITH pagedtable AS (");
sql.Append(GenerateCommandLine());
//Since this class is for Oracle-specific SQL, we can add a hint
//which should help pagination queries return rows more quickly.
//AFAIK, this is only valid for Oracle 9i or newer.
sql.Replace("SELECT", "SELECT /*+ first_rows('" + query.PageSize + "') */");
sql.Append(", ROW_NUMBER () OVER (");
sql.Append(orderBy);
sql.Append(") AS rowindex ");
sql.Append(Environment.NewLine);
sql.Append(GenerateFromList());
sql.Append(GenerateJoins());
sql.Append(GenerateWhere());
if (query.Aggregates.Count > 0)
{
sql.Append(GenerateGroupBy());
sql.Append(Environment.NewLine);
sql.Append(GenerateHaving());
}
sql.Append(") SELECT * FROM pagedtable WHERE rowindex >= ");
sql.Append(startnum);
sql.Append(" AND rowindex < ");
sql.Append(endnum);
sql.Append(" ORDER BY rowindex");
return sql.ToString();
}
The above worked for me. Hope this helps others!
Oracle would be fine with the Top-N query if the inner SQL would consist of the first statement (including where and order by).
So, i'd say there is no Oracle specific reason to omit them.
Never used subsonic, don't know if you need to do it different there.
Performance wise, an index on DISABLED_DATE, CREATED_DATE should do the trick (see: http://blog.fatalmind.com/2010/07/30/analytic-top-n-queries/).

Help with Parameterizing SQL Query for C# with possible null values

I need help with parameterizing this query.
SELECT *
FROM greatTable
WHERE field1 = #field1
AND field2 = #field2
The user should be able to search for any of the 2 fields, and the user also should be able to search if the field2 has null values.
var query = "theQuery";
var cm = new SqlCommand(cn, query);
cm.AddParameter("#field1", "352515");
cm.AddParameter("#field2", DBNull.Value);
// my DataTable here is having 0 records
var dt = GetTable(cm);
[Edit]
What is the best alternative?
Keep CommandText constant so the plan in Sql be reused
WHERE (field2 = #field2 OR #field2 IS NULL)
Change CommandText dynamically based on the values introduced by the user.
WHERE field2 IS NULL
I'm not just thinking in one field, it could be various.
You can use AND (#field2 IS NULL OR field2 = #field2) to make the query return all rows without checking field2 (to allow you to pass DbNull from your code.
The complete query would be something like this:
SELECT *
FROM greatTable
WHERE field1 = #field1
AND (#field2 IS NULL OR field2 = #field2)
Note that when using this method there might be a performance-hit because of indexing. Take a look at this article for details.
A dirty method:
isnull(field2,0) = isnull(#param2,0)
have the isnull something that would never be in the field or param
A good question, but in practice I had no time such problem. If I use AddParameter then I also construct the string for the query in the same code fragment. So if I know, that now I should search for "#field2" equal to NULL I decide whether to construct
string query = "SELECT * " +
"FROM greatTable " +
"WHERE field1 = #field1";
or
string query = "SELECT * " +
"FROM greatTable " +
"WHERE field1 = #field1 AND field2 IS NULL";
and add only one parameter
cm.AddParameter("#field1", "352515");
So I have no time the problem which you describe.
UPDATED: What is the best (or the only correct) way is users input is NULL you should decide based on the context. In the most cases "AND field2 IS NULL" is not needed, but I read your question so, that in your special case the user can explicitly choose not "" (empty string), but a NULL value.

Oracle/c#: How do i use bind variables with select statements to return multiple records?

I have a question regarding Oracle bind variables and select statements.
What I would like to achieve is do a select on a number different values for the primary key. I would like to pass these values via an array using bind values.
select * from tb_customers where cust_id = :1
int[] cust_id = { 11, 23, 31, 44 , 51 };
I then bind a DataReader to get the values into a table.
The problem is that the resulting table only contains a single record (for cust_id=51). Thus it seems that each statement is executed independently (as it should), but I would like the results to be available as a collective (single table).
A workaround is to create a temporary table, insert all the values of cust_id and then do a join against tb_customers. The problem with this approach is that I would require temporary tables for every different type of primary key, as I would like to use this against a number of tables (some even have combined primary keys).
Is there anything I am missing?
Not asking the question as to why you would want to do this to begin with. Shouldn't the sql statement be something like
select * from tb_customers where cust_id = 11 or 23 or ...
Edit:
I am limited in Oracle but when I look at the documentation I think that you might have to do something like this:
variable i number
exec :i := 11
select * from tb_customers where cust_id = :i
This would allow you to take advantage of binding. You will have to add each record return to your own collection since it will still only return one at a time.
I know this was asked a while ago but not a brilliant answer.
I would do something like this - please excuse the crude psudo code
string bindList = "";
for(int ii=0;ii<cust_id.count;++ii)
{
if(ii == 0)
{
bindList += ":" + ii;
}
else
{
bindList += ",:" + ii;
}
OracleParameter param = new OracleParameter();
param.dbType = types.int;
param.value = cust_id[ii];
command.Parameters.Add(param);
}
query = "select * from tb_customers where cust_id in(" + bindList + ")";
So then query ends up having in(:1,:2,:3,etc) and each of these are bound separately.
There is also a similar question here: OracleParameter and IN Clause

Oracle Parameters with IN statement?

Got a c#.net app which I need to modify. The query at the moment effectively does this:
select * from contract where contractnum = :ContractNum
(very simplified, just to show we're using an = and one parameter)
That parameter is read in from the Settings.Settings file on the C# app and has one string in it. I need to modify it to include multiple contracts, so I figure I can change the SQL to:
select * from contract where contractnum in (:ContractNum)
but that returns no results, no matter how I format the string in the parameter.
Is there a way I can get oracle to do an IN with a parameter?
You can use an Oracle collection of numbers as a parameter (bind variable) when you use ODP.NET as dataprovider. This works with Oracle server 9, 10 or 11 and ODP.net release >= 11.1.0.6.20 .
A similar solution is possible when you use Devart's .NET dataprovider for Oracle.
Let's select the contracts with contractnum's 3 and 4.
We have to use an Oracle type to transfer an array of contract numbers to our query.
MDSYS.SDO_ELEM_INFO_ARRAY is used because if we use this already predefined Oracle type we don't have to define our own Oracle type. You can fill MDSYS.SDO_ELEM_INFO_ARRAY with max 1048576 numbers.
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;
[OracleCustomTypeMappingAttribute("MDSYS.SDO_ELEM_INFO_ARRAY")]
public class NumberArrayFactory : IOracleArrayTypeFactory
{
public Array CreateArray(int numElems)
{
return new Decimal[numElems];
}
public Array CreateStatusArray(int numElems)
{
return null;
}
}
private void Test()
{
OracleConnectionStringBuilder b = new OracleConnectionStringBuilder();
b.UserID = "sna";
b.Password = "sna";
b.DataSource = "ora11";
using (OracleConnection conn = new OracleConnection(b.ToString()))
{
conn.Open();
using (OracleCommand comm = conn.CreateCommand())
{
comm.CommandText =
#" select /*+ cardinality(tab 10) */ c.* " +
#" from contract c, table(:1) tab " +
#" where c.contractnum = tab.column_value";
OracleParameter p = new OracleParameter();
p.OracleDbType = OracleDbType.Array;
p.Direction = ParameterDirection.Input;
p.UdtTypeName = "MDSYS.SDO_ELEM_INFO_ARRAY";
//select contract 3 and 4
p.Value = new Decimal[] { 3, 4 };
comm.Parameters.Add(p);
int numContracts = 0;
using (OracleDataReader reader = comm.ExecuteReader())
{
while (reader.Read())
{
numContracts++;
}
}
conn.Close();
}
}
}
The index on contract.contractnum isn't used when one omits hint /*+ cardinality(tab 10) */. I assumed contractnum is the primary key so this column will be indexed.
See also here: http://forums.oracle.com/forums/thread.jspa?messageID=3869879#3869879
you could use a pipelined function to transform a string into a table which could be used with the IN operator. For example (tested with 10gR2):
SQL> select * from table(demo_pkg.string_to_tab('i,j,k'));
COLUMN_VALUE
-----------------
i
j
k
with the following package:
SQL> CREATE OR REPLACE PACKAGE demo_pkg IS
2 TYPE varchar_tab IS TABLE OF VARCHAR2(4000);
3 FUNCTION string_to_tab(p_string VARCHAR2,
4 p_delimiter VARCHAR2 DEFAULT ',')
5 RETURN varchar_tab PIPELINED;
6 END demo_pkg;
7 /
Package created
SQL> CREATE OR REPLACE PACKAGE BODY demo_pkg IS
2 FUNCTION string_to_tab(p_string VARCHAR2,
3 p_delimiter VARCHAR2 DEFAULT ',')
4 RETURN varchar_tab PIPELINED IS
5 l_string VARCHAR2(4000) := p_string;
6 l_first_delimiter NUMBER := instr(p_string, p_delimiter);
7 BEGIN
8 LOOP
9 IF nvl(l_first_delimiter,0) = 0 THEN
10 PIPE ROW(l_string);
11 RETURN;
12 END IF;
13 PIPE ROW(substr(l_string, 1, l_first_delimiter - 1));
14 l_string := substr(l_string, l_first_delimiter + 1);
15 l_first_delimiter := instr(l_string, p_delimiter);
16 END LOOP;
17 END;
18 END demo_pkg;
19 /
Package body created
Your query would look like this:
select *
from contract
where contractnum in (select column_value
from table(demo_pkg.string_to_tab(:ContractNum)))
Have yet to find a db that supports evaluating a single string variable containing commas to separate as the sole IN clause.
Your options are to substring the variable so the comma delimited variable contents are turned into rows, so you can then join onto this. Or to use dynamic SQL, which is a SQL statement constructed as a string in a sproc before the statement is executed.
I know this is an old question but it is one of several in which the selected answer did not solve my problem and I don't want to start yet another thread on this topic so I'll just put down what I found in my travels in the hope that it might help someone.
I don't work with Oracle much but, like in SQL Server, it seems that to pass a table-valued parameter you need to have a corresponding UDT (user defined table) to which you have EXECUTE permissions (I could be wrong). This means that other answers suggesting the use of a built-in SYS UDT come with some freight and I couldn't figure out whether it really is possible to pass a table to something that is not a PL/SQL stored procedure in the current version of ODP.net.
Second, the string-parse solution is a kludge for all the obvious reasons (can't cache the execution plan or whatever Oracle calls it, doesn't scale well, etc).
So I spent rather a lot of time trying do the IN-clause using a table-valued parameter on a datamart to which I have only READ permission before I was hit by a blinding flash of the obvious (At an ASP.net forum no less). Turns out Oracle supports Xml queries 'natively' so instead of passing an array of values you can pass an xml list (if that is all you need). Again, I may be wrong, but it gets handled as a legitimate bind parameter and this is an example of how simple it is to use (vb.net, ADO.net, ODP.net using NuGet package):
Dim xe As New XElement("l", New XElement("i", "ITEM-A"), New XElement("i", "ITEM-B"))
Using conn As New OracleConnection(myConnectionString)
conn.Open()
Using cmd As OracleCommand = conn.CreateCommand()
cmd.CommandType = CommandType.Text
Dim query As String
query = " SELECT s.FOO, q.BAR " & vbCrLf
query &= " FROM TABLE1 s LEFT OUTER JOIN " & vbCrLf
query &= " TABLE2 q ON q.ID = s.ID " & vbCrLf
query &= " WHERE (COALESCE(q.ID, 'NULL') NOT LIKE '%OPTIONAL%') AND "
query &= " (s.ID IN ("
query &= " SELECT stid "
query &= " FROM XMLTable('/l/i' PASSING XMLTYPE(:stid) COLUMNS stid VARCHAR(32) PATH '.')"
query &= " )"
query &= " )"
cmd.CommandText = query
Dim parameter As OracleParameter = cmd.Parameters.Add("stid", OracleDbType.NVarchar2, 4000)
parameter.Value = xe.ToString
Using r As OracleDataReader = cmd.ExecuteReader
While r.Read()
//Do something
End While
End Using
End Using
conn.Close()
This is more of an observation than a carefully researched solution so please comment if there is anything inappropriate about doing it this way.
There is apparently a 4000 character limit using this method (2000 if NVARCHAR) so I had to watch my paging. The informative error message you get if you go over is
ORA-01460: unimplemented or unreasonable conversion requested
For using parameter with IN statement you can use this construction:
select * from contract where contractnum
in (select column_value from table (:ContractNum))
where ContractNum is the custom array type.
Maybe someone is still looking for an answer, here's an example with rexexp.
https://blogs.oracle.com/aramamoo/passing-comma-separated-string-as-bind-variable-for-vo-querys-in-operator-v2
In this case, each emp no is coma separated
WHERE Emp.ENAME in
(select regexp_substr(:Bind_Ename_Comma_Sep_List,'[^,]+', 1, level)
from dual
connect by
regexp_substr(:Bind_Ename_Comma_Sep_List, '[^,]+', 1, level)
is not null)
Another way is to use the INSTR function: For example if the comma delimited data is in :xyz then you can determine if a value in a named item fldOne is in that list as follows:
INSTR(',' || :xyz || ',', ',' || fldOne ||',') > 0
will return true if the value fldOne references is in the list and false otherwise.
Of course, if you want to match against an explicit string, then you can replace fldOne with 'value'.
BTW this will also work if you created a comma separated variable using ListAgg or any other mechanism.
If the comma separated list was named MyList then just replace :xyz above with MyList.

Categories