IDbCommand and generic Parameters
Here is something that I’ve run into a few times and have not been able to resolve generically. ADO.Net support generic interfaces for most of the data access components to allow writing generic front end code against specific data providers. The idea is that you can cast down any of the specific providers to a generic interface and then use the generic versions in your front end code.
I make use of this extensively in my Business Object layer which talks to a data layer. The data layer can then use any of the supported backend providers (SqlClient, OleDbClient, OdbcClient, MySqlClient etc.) interchangeably and not worry about a specific implementation.
Since this happens in the business layer this also means that any customizations should use that approach. It works well for the most part, with the exception of parameter objects, which is also one of the most common scenarios. Here’s an example of a business object method accessing the Data access layer (which is wrapped in the This.Execute, this.CreateCommand etc) :
public bool LoadByInvNo(string InvNo)
{
this.SetError();
if (!this.Open())
return false;
IDbCommand Command =
this.CreateCommand("select pk from wws_invoice where invno=@InvNo");
// *** Note SQL Specific code
SqlParameterCollection Parms = (SqlParameterCollection) Command.Parameters;
Parms.Add("@InvNo",InvNo);
if (this.Execute(Command,"TInvNoPk") < 1)
{
return false;
}
int Pk = (int) this.DataSet.Tables["TInvNoPk"].Rows[0][0];
return this.Load(Pk);
}
Notice the IDbCommand instead of the SqlCommand object returned from the data layer (and wrappered in the local CreateCommand method of the bus object). But once you have this Command object there’s no way to directly retrieve a parameter object.
The code above explicitly uses a SqlParameterCollection object, but this actually hardcodes the above code to SQL Server. This is hardly ideal.
I did this because the following does not work (as the docs suggest):
int index = Command.Parms.Add("@InvNo");
IDbDataParameter Parm = (IDbDataParameter) Command.Parms[index];
Parm.Value = InvNo;
Following the docs on what’s required for the generic Add is futile. What’s actually required as a parameter is not a parameter name or value (what MSDN says) but an IDbDataParameter object. The problem is there’s no way to directly get a IDbDataParameter out of the Parameters collection, which is too bad.
You can do this with the provider specific parameter collections though, as shown in the first sample, by doing Command.Parameters.Add() which returns you a reference to a new parameter object:
SqlParameterCollection Parms = (SqlParameterCollection) Command.Parameters;
IDbDataParameter Parm = Parms.Add("@InvNo",InvNo);
Parm.SourceColumn = "InvNo";
But this of course is provider specifc – this only works with SQL Server or OleDb or Oracle depending on which type of Parameter collection you’re referencing.
My workaround is to provide a parameter option from the Data Access layer.
IDbDataParameter Parm = this.Data.CreateParameter("@InvNo");
Parm.Value = InvNo;
Command.Parameters.Add(Parm);
But it seems silly that you have to do this since the Command object already contains the Parameters collection of the right type.
This works fine in the above scenario since I have a Data layer that feeds me these objects via Factory methods anyway. But this problem actually arose yesterday because I was working on a generic DataRow update routine that bypasses the DataAdapter for updates. I created a static method UpdataDataRow() into which I pass a DataRow, a IDbCommand object and a few options. Since it’s static it can’t use a Factory method to retrieve a parameter object which ruins the whole idea of having a generic static method for this.
Maybe there’s some way to do this and I’m just missing it – for now the above works.
?>Other Posts you might also like
The Voices of Reason
# re: IDbCommand and generic Parameters
IDbCommand cmd = Database.Connection.CreateCommand();
cmd.CommandText = sql;
cmd.CommandType = CommandType.Text;
IDbDataParameter p = cmd.CreateParameter();
p.ParemeterName="@columnA";
p.DbType = DbType.String;
p.Value = "column value";
cmd.Parameters.Add(p);
IDataReader reader = cmd.ExxcuteReader();
while(reader.Read()){
Console.Write(reader.GetString(0));
}
reader.Close();
If it works for u, let me know pls.
du_d_u@yahoo.com.cn
These segments work for me.
BTW, Database class create provider specific connection, but return IDbConnection.
# re: IDbCommand and generic Parameters
# re: IDbCommand and generic Parameters
And if you don't want to access the layer for every parameter, you still could use something like this:
NameValueCollection params = new NameValueCollectoin();
params.Add("ParamName1", value1);
params.Add("ParamName2", value2);
and feed both to the data layer
IDbCommand cmd = CommandBuilder.Create(sql, params)
Cheers,
Philipp
# re: IDbCommand and generic Parameters
Precision
Scale
Size
What am I missing?
# Mr
DbParameter should be provider independent based on the current factory used. eg
Dim loCmd As DbCommand = GenericFactoryHelper.Command 'generic based on current facotry Dim loPram As DbParameter = loCmd.CreateParameter 'generic based on current factory loPram.ParameterName = GenericFactoryHelper.FormatParameter("MyPram") ' this is the trick, format it dynamically loPram.Value = "something" loCmd.Parameters.Add(loPram) ' Format the DB parameters based on the provider format eg SQL is @ ' or what ever way you want to read the ParameterMarkerFormat private Shared Function FormatParameter(ByVal ParameterName As String) As String Dim ParameterMarkerFormat As String Dim loCnn As DbConnection = OpenConnection() ParameterMarkerFormat = loCnn.GetSchema("DataSourceInformation").Rows(0)("ParameterMarkerFormat").ToString Return String.Format(ParameterMarkerFormat, ParameterName) End Function
This will work ok as I am not using '@MyPram' or :MyPram ...
rahman
# re: IDbCommand and generic Parameters
# re: IDbCommand and generic Parameters
I'm glad you found that .net 2.0 provides all the generic classes for ADO.net.
Would you care to illustrate with an example to your original problem?
Thanks.
# re: IDbCommand and generic Parameters
using (var connection = new SQLiteConnection("data source=:memory:;")) { connection.Open(); using (IDbCommand command = connection.CreateCommand()) { command.CommandText = "DROP TABLE IF EXISTS Table1;" + "create table IF NOT EXISTS Table1(Value1 INTEGER not null,Value2 INTEGER not null);"; command.ExecuteNonQuery(); } using (IDbTransaction transaction = connection.BeginTransaction()) { using (IDbCommand command = connection.CreateCommand()) { command.CommandText = "INSERT INTO Table1 values (@Param1,@Param2)"; IDbDataParameter tempParam1 = command.CreateParameter(); tempParam1.ParameterName = "Param1"; command.Parameters.Add(tempParam1); IDbDataParameter tempParam2 = command.CreateParameter(); tempParam2.ParameterName = "Param2"; command.Parameters.Add(tempParam2); for(int i = 1; i <= 12; i++) { ((IDbDataParameter)command.Parameters["Param1"]).Value = i; ((IDbDataParameter)command.Parameters["Param2"]).Value = i * i; command.ExecuteNonQuery(); } transaction.Commit(); } } connection.Close(); }
The key is the cast:
Does anyone know why IDbCommand.Parameters[] returns an object?
((IDbDataParameter)command.Parameters["Param1"]).Value = i;
# re: IDbCommand and generic Parameters
Hi Rick, Have you considered the option of just making your own objects out of the interfaces?
Thank you, Rob
public class DbDataParameter : DataParameter, IDbDataParameter
{
public byte Precision { get; set; }
public byte Scale { get; set; }
public int Size { get; set; }
}
# re: IDbCommand and generic Parameters