cmd.Parameters.Value not using type - npgsql

Hi With version 6 of npgsql, a command like cmd.Parameters[i].Value = value requires that value is a variable of the same type as previous cmd.Parameters.Add(name, type)
Another way to explain it; in
cmd.Parameters.AddWithValue(keycol, NpgsqlTypes.NpgsqlDbType.Bigint, keyid);
keyid must be of type bigint, can't be a string, even it is a compatible bigint like "12454"
This was not the case in version 3, where you were able to add all parameters like this
cmd.Parameters[i].Value = string, regardless type inside type, of course supposing it's compatible.
This makes code less simple as you have to test each type and use an according vaiable to insert
switch (champ.NpgType) {
case NpgsqlTypes.NpgsqlDbType.Integer:
cmd.Parameters.Add(new NpgsqlParameter<Int32>(champ.Column, champ.NpgType) { TypedValue = Convert.ToInt32(champ.Value) });
break;
case NpgsqlTypes.NpgsqlDbType.Bigint:
cmd.Parameters.Add(new NpgsqlParameter<Int64>(champ.Column, champ.NpgType) { TypedValue = Convert.ToInt64(champ.Value)});
break;
case NpgsqlTypes.NpgsqlDbType.Boolean:
cmd.Parameters.Add(new NpgsqlParameter<Boolean>(champ.Column, champ.NpgType) { TypedValue = Convert.ToBoolean(champ.Value) });
break;
case NpgsqlTypes.NpgsqlDbType.Double:
case NpgsqlTypes.NpgsqlDbType.Numeric:
cmd.Parameters.Add(new NpgsqlParameter<Double>(champ.Column, champ.NpgType) { TypedValue = Convert.ToDouble(champ.Value) });
break;
case NpgsqlTypes.NpgsqlDbType.Timestamp:
case NpgsqlTypes.NpgsqlDbType.Date:
cmd.Parameters.Add(new NpgsqlParameter<DateTime>(champ.Column, champ.NpgType) { TypedValue = Convert.ToDateTime(champ.Value) });
break;
default:
cmd.Parameters.Add(new NpgsqlParameter<String>(champ.Column, champ.NpgType) { TypedValue = champ.Value });
break;
}
Am I right ?
Is there a more simple way to do that ? Why version 6 does not behave like version 3
Thanks
Olivier

This is by-design: Npgsql takes a strongly-typed approach to data, and will not perform implicit conversions for you (e.g. string to int). Doing so can make it easy to accidentally write incorrect data or unintentionally perform conversions which are costly in terms of performance. Just like C# doesn't allow assigning a string to an int, Npgsql doesn't allow sending a string as an int.
Older versions of Npgsql may have been more lax on this kind of this, that's possible.

Related

An array of enum members using implicit member syntax

Consider this enum.
enum FilmGenre: String, CaseIterable {
case horror = "Horror"
case comedy = "Comedy"
case animation = "Animation"
case romance = "Romance"
case fantasy = "Fantasy"
case adventure = "Adventure"
}
Is there a way to write it like this?
let filmGenres: [FilmGenre.RawValue] = [.horror.rawValue,
.comedy.rawValue,
.animation.rawValue]
The compiler complains with an error:
Type 'FilmGenre.RawValue' (aka 'String') has no member 'horror'
The best I can do is like this.
let filmGenres: [FilmGenre.RawValue] = [FilmGenre.horror.rawValue,
FilmGenre.comedy.rawValue,
FilmGenre.animation.rawValue]
I've tried various combinations from the auto-complete.
let filmGenres: [FilmGenre.AllCases.Element.RawValue] = [...]
Is it not possible to do it in Swift 5.4?
[FilmGenre.RawValue] translates to [String] in the case and clearly String doesn't know anything about .horror that is defined in other type FilmGenre.
What you can do is -
enum FilmGenre: String, CaseIterable {
case horror = "Horror"
case comedy = "Comedy"
case animation = "Animation"
case romance = "Romance"
case fantasy = "Fantasy"
case adventure = "Adventure"
}
/// Have a variable that is of `[FilmGenre]` type
/// This will allow you to use the type safety you are looking for
let filmGenres: [FilmGenre] = [.horror, .comedy, .animation]
/// This one you can use anywhere else as you like
/// This will give you `[String]` which is what you want in this case
let filmGenreRawValues = filmGenres.map({ $0.rawValue })
You can add a static function with var args to your enum for a more dynamic way to create the array
static func arrayWithRawValues(_ genre: FilmGenre...) -> [Self.RawValue] {
genre.map(\.rawValue)
}
Example
print(FilmGenre.arrayWithRawValues(.horror, .comedy, .animation))
Output
["Horror", "Comedy", "Animation"]

Want to do an action if a table value equals expected if no perform another action e.g clear fields

I am fairly new to Selenium and am self teaching, I have used a little JAVA years ago
I want to add a statement around value1 to perform an action if value is as expected and perform a different action if not
driver.findElement(By.id("task-table-filter")).sendKeys("test1");
WebElement firstName = driver.findElement(By.xpath("//*[#id=\"task-table\"]/tbody/tr[8]/td"));
String value = firstName.getText();
System.out.println(value);
driver.findElement(By.xpath("//*[#id=\"task-table-filter\"]")).clear();
driver.findElement(By.id("task-table-filter")).sendKeys("test2");
WebElement searchname = driver.findElement(By.xpath("//*[#id=\"task-table\"]/tbody/tr[6]/td[3]"));
String value1 = searchname.getText();
System.out.println(value1);
You can use a simple if/else
if (value1.equals("expected value")) {
do.action;
} else {
do.otherActon;
}

Convert/add string to a specific type

I would like to convert/add a string to a type [Talent.Otherlanguages]
Talent.Otherlanguages is an enum who contain many languages.
I would like to do this : otherlanguages?.append(Talent.Otherlanguage(rawValue: langue)!)
but when i do print(otherlanguages) the value is set to nil.
Do any of you have an idea to help me ?
In case you consider otherlanguages is nil it is my assamtion. Because if you send wrong rawValue to the enum constructor you going to receive crash.So you are not checking if otherlanguages is not nil and try to append something.
This is example:
enum Languages:String {
case uk = "english "
case ua = "ukrainian"
}
var languages = [Languages]()
print(languages) //[]
languages.append(Languages(rawValue: "ukrainian")!)
print(languages) //[Languages.ua]

How to use string indexing with IDataReader in F#?

I'm new to F# and trying to dive in first and do a more formal introduction later. I have the following code:
type Person =
{
Id: int
Name: string
}
let GetPeople() =
//seq {
use conn = new SQLiteConnection(connectionString)
use cmd = new SQLiteCommand(sql, conn)
cmd.CommandType <- CommandType.Text
conn.Open()
use reader = cmd.ExecuteReader()
let mutable x = {Id = 1; Name = "Mary"; }
while reader.Read() do
let y = 0
// breakpoint here
x <- {
Id = unbox<int>(reader.["id"])
Name = unbox<string>(reader.["name"])
}
x
//}
let y = GetPeople()
I plan to replace the loop body with a yield statement and clean up the code. But right now I'm just trying to make sure the data access works by debugging the code and looking at the datareader. Currently I'm getting a System.InvalidCastException. When I put a breakpoint at the point indicated by the commented line above, and then type in the immediate windows reader["name"] I get a valid value from the database so I know it's connecting to the db ok. However if I try to put reader["name"] (as opposed to reader.["name"]) in the source file I get "This value is not a function and cannot be applied" message.
Why can I use reader["name"] in the immediate window but not in my fsharp code? How can I use string indexing with the reader?
Update
Following Jack P.'s advice I split out the code into separate lines and now I see where the error occurs:
let id = reader.["id"]
let id_unboxed = unbox id // <--- error on this line
id has the type object {long} according to the debugger.
Jack already answered the question regarding different syntax for indexing in F# and in the immediate window or watches, so I'll skip that.
In my experience, the most common reason for getting System.InvalidCastException when reading data from a database is that the value returned by reader.["xyz"] is actually DbNull.Value instead of an actual string or integer. Casting DbNull.Value to integer or string will fail (because it is a special value), so if you're working with nullable columns, you need to check this explicitly:
let name = reader.["name"]
let name_unboxed : string =
if name = DbNull.Value then null else unbox name
You can make the code nicer by defining the ? operator which allows you to write reader?name to perform the lookup. If you're dealing with nulls you can also use reader?name defaultValue with the following definition:
let (?) (reader:IDataReader) (name:string) (def:'R) : 'R =
let v = reader.[name]
if Object.Equals(v, DBNull.Value) then def
else unbox v
The code then becomes:
let name = reader?name null
let id = reader?id -1
This should also simplify debugging as you can step into the implementation of ? and see what is going on.
You can use reader["name"] in the immediate window because the immediate window uses C# syntax, not F# syntax.
One thing to note: since F# is much more concise than C#, there can be a lot going on within a single line. In other words, setting a breakpoint on the line may not help you narrow down the problem. In those cases, I normally "expand" the expression into multiple let-bindings on multiple lines; doing this makes it easier to step through the expression and find the cause of the problem (at which point, you can just make the change to your original one-liner).
What happens if you pull the item accesses and unbox calls out into their own let-bindings? For example:
while reader.Read() do
let y = 0
// breakpoint here
let id = reader.["id"]
let id_unboxed : int = unbox id
let name = reader.["name"]
let name_unboxed : string = unbox name
x <- { Id = id_unboxed; Name = name_unboxed; }
x

From .NET can I get the full SQL string generated by a SqlCommand object (with SQL Parameters)?

From the .NET environment can I get access to the full SQL string that is generated by a SqlCommand object?
Note: The full SQL string shows up in Intellisense hover, in VisualStudio, while in debug mode.
I'm willing to use reflection techniques if I must. I'm sure somebody here knows a way to get at it.
Update 1:
I'm calling a stored procedure having parameters with cmd.CommandType = CommandType.StoredProcedure and am trying to acquire the full SQL generated and run.
I wonder if the cmd.Prepare() method might not prove useful in this circumstance, if it might store the full string in a state field or something like that.
Update 2:
In light of answers below (and referenced) that indicate no complete SQL string is generated internally during preparation or execution, I did a bit of poking around using .NET Reflector. Even the internal connection classes seem to pass objects rather than boiling them down to strings, for example:
internal abstract void AddPreparedCommand(SqlCommand cmd);
Declaring Type: System.Data.SqlClient.SqlInternalConnection
Assembly: System.Data, Version=2.0.0.0
In general, thanks to everybody for the level of detail you got into to prove what can be done and show what's actually happening. Much appreciated. I like thorough explanations; they add surety and lend credence to the answers.
A simple loop replacing all the parameter names with their values will provide you with something similar to what the end result is, but there are several problems.
Since the SQL is never actually rebuilt using the parameter values, things like newlines and quotes don't need to be considered
Parameter names in comments are never actually processed for their value, but left as-is
With those in place, and taking into account parameter names that starts with the same characters, like #NAME and #NAME_FULL, we can replace all the parameter names with the value that would be in the place of that parameter:
string query = cmd.CommandText;
foreach (SqlParameter p in cmd.Parameters.OrderByDescending(p => p.ParameterName.Length))
{
query = query.Replace(p.ParameterName, p.Value.ToString());
}
there is one problem left with this, however, and that is if a parameter is a string, then the SQL that initially looks like this:
SELECT * FROM yourtable WHERE table_code = #CODE
will look like this:
SELECT * FROM yourtable WHERE table_code = SOME CODE WITH SPACES
This is clearly not legal SQL, so we need to account for some parameter-types as well:
DbType[] quotedParameterTypes = new DbType[] {
DbType.AnsiString, DbType.Date,
DbType.DateTime, DbType.Guid, DbType.String,
DbType.AnsiStringFixedLength, DbType.StringFixedLength
};
string query = cmd.CommandText;
var arrParams = new SqlParameter[cmd.Parameters.Count];
cmd.Parameters.CopyTo(arrParams, 0);
foreach (SqlParameter p in arrParams.OrderByDescending(p => p.ParameterName.Length))
{
string value = p.Value.ToString();
if (quotedParameterTypes.Contains(p.DbType))
value = "'" + value + "'";
query = query.Replace(p.ParameterName, value);
}
There have been a couple of similar questions here.
The most compelling answer was provided to this question: How to get the generated SQL-Statment from a SqlCommand-Object?
and the answer was:
You can't, because it does not
generate any SQL.
The parameterized query (the one in
CommandText) is sent to the SQL Server
as the equivalent of a prepared
statement. When you execute the
command, the parameters and the query
text are treated separately. At no
point in time a complete SQL string is
generated.
You can use SQL Profiler to take a
look behind the scenes.
The CommandText property (or calling ToString()) on your command will give you all of the SQL, with a small exception. It will definitely give you anything you see in the debugger. Note that this won't give you parameter values, but it will give you the actual command.
The only caveat is that when CommandType is Text, the ADO.NET framework will often (in fact, almost always) use sp_executesql to execute the command rather than executing the command directly against the connection. In that sense, it's not possible to obtain the exact SQL that gets executed.
I haven't tried this, but you may be able to use Capture Mode if you are willing to use SMO:
http://msdn.microsoft.com/en-us/library/ms162182(v=sql.120).aspx
I like Jesus Ramos answer, but I needed support for output parameters. (I also used a string builder to generate the content.)
Declare Parameter for output parameters
foreach (SqlParameter p in arrParams.Where(x => x.Direction == ParameterDirection.Output || x.Direction == ParameterDirection.InputOutput))
{
// todo : I only needed a couple of types supported, you could add addition types
string dbtype = string.Empty;
switch (p.DbType)
{
case DbType.Guid:
dbtype = "uniqueidentifier";
break;
case DbType.Int16:
case DbType.Int64:
case DbType.Int32:
dbtype = "int";
break;
case DbType.String:
dbtype = "varchar(max)";
break;
}
query.Append(string.Format(" Declare {0}_ {1}\n", p.ParameterName, dbtype));
}
Build Main Parameter Area
foreach (SqlParameter p in arrParams)
{
bool isLast = p == last;
string value = p.Value.ToString();
if (quotedParameterTypes.Contains(p.DbType))
value = "'" + value + "'";
if (p.Direction == ParameterDirection.InputOutput || p.Direction == ParameterDirection.Output)
{
query.Append(string.Format("{0} = {0}_ out{2}\n", p.ParameterName, value, isLast ? "" : ","));
}
else
{
query.Append(string.Format("{0} = {1}{2}\n", p.ParameterName, value, isLast ? "" : ","));
}
}
List Output Parameter results
foreach (SqlParameter p in arrParams.Where(x => x.Direction == ParameterDirection.Output || x.Direction == ParameterDirection.InputOutput))
{
query.Append(string.Format(" select {0}_ {1}\n", p.ParameterName, p.ParameterName.Substring(1)));
}
Full Code:
public static string GetProcedureDebugInformation(SqlCommand cmd, [System.Runtime.CompilerServices.CallerMemberName] string caller = null, [System.Runtime.CompilerServices.CallerFilePath] string filePath = null, [System.Runtime.CompilerServices.CallerLineNumber] int? lineNumber = null)
{
// Collection of parameters that should use quotes
DbType[] quotedParameterTypes = new DbType[] {
DbType.AnsiString, DbType.Date,
DbType.DateTime, DbType.Guid, DbType.String,
DbType.AnsiStringFixedLength, DbType.StringFixedLength
};
// String builder to contain generated string
StringBuilder query = new StringBuilder();
// Build some debugging information using free compiler information
query.Append(filePath != null ? filePath : ""
+ (lineNumber.HasValue ? lineNumber.Value.ToString() : "")
+ (lineNumber.HasValue || !string.IsNullOrWhiteSpace(filePath) ? "\n\n" : ""));
query.Append("\n\n");
var arrParams = new SqlParameter[cmd.Parameters.Count];
cmd.Parameters.CopyTo(arrParams, 0);
// Declare Parameter for output parameters
foreach (SqlParameter p in arrParams.Where(x => x.Direction == ParameterDirection.Output || x.Direction == ParameterDirection.InputOutput))
{
// todo : I only needed a couple of types supported, you could add addition types
string dbtype = string.Empty;
switch (p.DbType)
{
case DbType.Guid:
dbtype = "uniqueidentifier";
break;
case DbType.Int16:
case DbType.Int64:
case DbType.Int32:
dbtype = "int";
break;
case DbType.String:
dbtype = "varchar(max)";
break;
}
query.Append(string.Format(" Declare {0}_ {1}\n", p.ParameterName, dbtype));
}
// Set Exec Text
query.Append(string.Format("\n exec {0}\n", cmd.CommandText));
var last = arrParams.LastOrDefault();
//Build Main Parameter Area
foreach (SqlParameter p in arrParams.OrderByDescending(p => p.ParameterName.Length))
{
bool isLast = p == last;
string value = p.Value.ToString();
if (quotedParameterTypes.Contains(p.DbType))
value = "'" + value + "'";
if (p.Direction == ParameterDirection.InputOutput || p.Direction == ParameterDirection.Output)
{
query.Append(string.Format("{0} = {0}_ out{2}\n", p.ParameterName, value, isLast ? "" : ","));
}
else
{
query.Append(string.Format("{0} = {1}{2}\n", p.ParameterName, value, isLast ? "" : ","));
}
}
// List Output Parameter results
foreach (SqlParameter p in arrParams.Where(x => x.Direction == ParameterDirection.Output || x.Direction == ParameterDirection.InputOutput))
{
query.Append(string.Format(" select {0}_ {1}\n", p.ParameterName, p.ParameterName.Substring(1)));
}
return query.ToString();
}

Resources