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

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();
}

Related

Dapper.net with unknown quantity of array elements

What I'm trying to do it have a filter object that is populated like so
var filter = new Filter
{
ThingID = 1,
Keywords = new[] { "op", "s" }
};
And then be able to build up the query like this:
var sb = new StringBuilder();
sb.AppendLine("select * from Stuff where 1=1");
if (filter.ThingID.HasValue)
{
sb.AppendLine(" and ThingID = #ThingID");
}
if (filter.Keywords != null)
{
for (int i = 0; i < filter.Keywords.Length; i++)
{
string keyword = filter.Keywords[i];
if (!string.IsNullOrWhiteSpace(keyword))
{
sb.AppendLine(" and ( Model like '%' || #Keywords" + i + " || '%' )");
}
}
}
var sql = sb.ToString();
var results = Query<Stuff>(sql, filter).ToList();
This works fine if just the ThingID prop is populated, but as far as I can tell Dapper is not feeding the Keywords in as a parameter in any way. Is this possible with Dapper, or does it only work in the context of " where Keywords in #Keywords"?
Parameters need to match by name; you are adding parameters like #Keywords17, but there is no Keywords17 property for it to add. It doesn't interpret that as Keywords[17], if that is what you mean. There is some automatic expansion of flat arrays, but that is intended for expanding in #Keywords (although the expansion itself is not specific to in). There is not currently something that would help you automatically there; I would suggest DynamicPaameters instead:
var args = new DynamicParameters();
args.Add("ThingID", 1);
...
if (!string.IsNullOrWhiteSpace(keyword))
{
sb.AppendLine(" and ( Model like '%' || #Keywords" + i + " || '%' )");
args.Add("Keywords" + i, keyword);
}
...
var cmd = new CommandDefinition(sql, args, flags: CommandFlags.NoCache);
var results = Query<Stuff>(cmd).AsList();
note the subtle two changes at the end here - CommandFlags.NoCache will help avoid building lots of lookup entries for similar but different SQL (although you might choose to pay this to reduce per-item cost, up to you). The AsList instead of ToList avoids an extra list allocation.

Passing a full-text search parameter using Dapper.net

Consider this simple query which use full text searching on the Keywords field:
DECLARE #searchTerm VARCHAR(500) = 'painted'
SELECT * FROM StockCatalogueItems
WHERE (CONTAINS(KeyWords, #searchTerm))
This works as expected, but I need to do the same using a Dapper.net parameterised query. When using stored procedures, I create the full text parameter like this: "\"painted*\""
But using the same approach this doesn't work using dapper. No results are returned. This is the line in the query where I use the parameter:
AND (CONTAINS(KeyWords, #search))
and it's passed to the query like so:
return _context.Database.Connection.Query<StockProfileMatrix>(basequery, new
{
search = searchTerm
}
I can only assume that dapper is sanitising the string somehow, removing quotes perhaps?
Any ideas?
This works for me. However the tech stack am working on is .net core RTM and "Dapper": "1.50.0-rc3",
_dbConnection.QueryAsync<Guid>(#"select br.Id from Brand br where CONTAINS(br.Text,#Name)",new {Name = $"\"*{name}*\""}))
For completeness, I'll answer the question. The query syntax is correct, but the way in which the full-text parameter is created was obviously not. I created an extension method that formats the parameter:
public static string ToFullText(this string str)
{
string searchTerm = null;
if (!string.IsNullOrEmpty(str))
{
string[] keywords = str.Trim().Split(null);
foreach (var keyword in keywords)
{
searchTerm += string.Format("\"{0}*\" AND ", keyword);
}
if (searchTerm != null)
searchTerm = searchTerm.Substring(0, searchTerm.LastIndexOf(" AND "));
}
return searchTerm;
}
Then I call the method when I pass the parameter in to the dapper query:
_context.Database.Connection.Query<dynamic>(query, new
{
search = filter.SearchTerm.ToFullText()
});

How to set Query Parameters

How to map the OLE DB source SQL command query parameters with variables using EzAPI ?
Basically I need to do something like below.
Thanks in advance.
Here is how I had to do it for SSIS 2012. I had to find the GUID of the variable in question and set it that way.
EzOleDbSource source = new EzOleDbSource(this);
source.Connection = sourceconnection;
source.SqlCommand = sourcecomannd;
source.AccessMode = AccessMode.AM_SQLCOMMAND;
source.SetComponentProperty("ParameterMapping", "\"Parameter0:Input\",{C2BCD5B0-1FDB-4A74-8418-EEF9C1D19AC3};");
To get the GUID you can query the Variables in the EZPackage object.
Application a = new Application();
var package = a.LoadPackage(packagelocation, null);
var ezpackage = new EzPackage(package);
var firstOrDefault = ezpackage.Variables.OfType<Microsoft.SqlServer.Dts.Runtime.Variable>()
.AsQueryable()
.FirstOrDefault(x => x.Name.Equals("MyParameter"));
if (firstOrDefault != null)
{
var guid =
firstOrDefault.ID;
}
I have accepted Geek's answer because it is the real answer for my question. But I found instead of mapping variables to parameters we can use variables directly in the query. For example: exec your_sp_name "#[User::your_variable_name]"
UPDATE : Above method is not working.
Use the the accepted answer method. To take the GUID of the variable, I used the follwing method.
string guid = string.Empty;
foreach (var x in this.Variables)
{
if (x.Name == "cdc_capture_log_id")
{
guid = x.ID;
}
}
Where this = EzPackage. Same as above.

Using LINQ to find Excel columns that don't exist in array?

I have a solution that works for what I want, but I'm hoping to get some slick LINQ types to help me improve what I have, and learn something new in the process.
The code below is used verify that certain column names exist on a spreadsheet. I was torn between using column index values or column names to find them. They both have good and bad points, but decided to go with column names. They'll always exist, and sometimes in different order, though I'm working on this.
Details:
GetData() method returns a DataTable from the Excel spreadsheet. I cycle through all the required field names from my array, looking to see if it matches with something in the column collection on the spreadsheet. If not, then I append the missing column name to an output parameter from the method. I need both the boolean value and the missing fields variable, and I wasn't sure of a better way than using the output parameter. I then remove the last comma from the appended string for the display on the UI. If the StringBuilder object isn't null (I could have used the missingFieldCounter too) then I know there's at least one missing field, bool will be false. Otherwise, I just return output param as empty, and method as true.
So, Is there a more slick, all-in-one way to check if fields are missing, and somehow report on them?
private bool ValidateFile(out string errorFields)
{
data = GetData();
List<string> requiredNames = new [] { "Site AB#", "Site#", "Site Name", "Address", "City", "St", "Zip" }.ToList();
StringBuilder missingFields = null;
var missingFieldCounter = 0;
foreach (var name in requiredNames)
{
var foundColumn = from DataColumn c in data.Columns
where c.ColumnName == name
select c;
if (!foundColumn.Any())
{
if (missingFields == null)
missingFields = new StringBuilder();
missingFieldCounter++;
missingFields.Append(name + ",");
}
}
if (missingFields != null)
{
errorFields = missingFields.ToString().Substring(0, (missingFields.ToString().Length - 1));
return false;
}
errorFields = string.Empty;
return true;
}
Here is the linq solution that makes the same.
I call the ToArray() function to activate the linq statement
(from col in requiredNames.Except(
from dataCol in data
select dataCol.ColumnName
)
select missingFields.Append(col + ", ")
).ToArray();
errorFields = missingFields.ToString();
Console.WriteLine(errorFields);

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

Resources