Dapper.net with unknown quantity of array elements - arrays

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.

Related

Apex, SOQL, add results from database to the List, error

I would like to add results from database to the List. But there is a bug.
String str = '\'AOC\',\'BPD\',\'CRE\'';
List<String> lstString = str.split(',');
List<Shop_Product__c> productList = new List<Shop_Product__c>();
Integer i = 0;
for (String record: lstString) {
System.debug('record[' + i + ']: ' + record);
if ((record != null) && (productList != null)) {
productList.add([SELECT Id, Brand__c
FROM Shop_Product__c
WHERE Brand__c = :record
LIMIT 10]);
System.debug('productList[' + i + ']: ' + productList);
System.debug('Here in for ----------------------------------------');
}
++i;
}
Error is System.QueryException: List has no rows for assignment to SObject.
Here is it the explain, but I don't understand what I should do.
The List method add() takes a single sObject. That's why you get the QueryException, just like in the examples you link to.
You can use the addAll() method to create a List<sObject> context, which avoids the exception if there are no responsive records.

How do I build a dynamic sql query with Dapper.SqlBuilder and OrWhere

I am attempting to build a dynamic Sql query for multiple search terms. I understand in general how to use the builder, but am not sure what to do in the loop since I actually need the #term to be different each time (I think). Not just in the query, but in the anonymous type as well to match.
I could use a string.Format in the query string, but not sure how to match it in the anonymous type?
public async Task<List<Thing>> Search(params string[] searchTerms)
{
var builder = new SqlBuilder();
var template = builder.AddTemplate("SELECT * /**select**/ from ThingTags /**where**/ ");
for (int i = 0; i < searchTerms.Length; i++)
{
builder.OrWhere("value LIKE #term", new { term = "%" + searchTerms[i] + "%" });
}
...
}
in the current form the query that gets created for terms "abc" "def" "ghi" is
CommandType: Text, CommandText: SELECT * from ThingTags WHERE ( value LIKE #term OR value LIKE #term OR value LIKE #term )
Parameters:
Name: term, Value: %ghi%
Well here is one way to do the query building. I didn't realize that the parameters could be a Dictionary initially.
public async Task<List<Thing>> Search(params string[] searchTerms)
{
var builder = new SqlBuilder();
var template = builder.AddTemplate("SELECT * /**select**/ from ThingTags /**where**/ ");
for (int i = 0; i < searchTerms.Length; i++)
{
var args = new Dictionary<string, object>();
var termId = string.Format("term{0}", i.ToString());
args.Add(termId, "%" + searchTerms[i] + "%");
builder.OrWhere("value LIKE #" + termId, args);
}
...
}
You can easily create that dynamic condition using DapperQueryBuilder:
var query = cn.QueryBuilder($#"
SELECT *
FROM ThingTags
/**where**/");
// by default multiple filters are combined with AND
query.FiltersType = Filters.FiltersType.OR;
foreach (var searchTerm in searchTerms)
query.Where($"value like {searchTerm}");
var results = query.Query<YourPOCO>();
The output is fully parametrized SQL (WHERE value like #p0 OR value like #p1 OR...). You don't have to manually manage the dictionary of parameters.
Disclaimer: I'm one of the authors of this library

Dapper SqlBuilder OrWhere using AND instead of OR

I was trying to use the Where and OrWhere methods of SqlBuilder for Dapper, but it is not acting like how I would expect.
The edited portion of this question is basically what I ran into. Since it didn't receive a response, I'll ask it here.
var builder = new SqlBuilder();
var sql = builder.AddTemplate("select * from table /**where**/ ");
builder.Where("a = #a", new { a = 1 })
.OrWhere("b = #b", new { b = 2 });
I expected select * from table WHERE a = #a OR b = #b
but I got select * from table WHERE a = #a AND b = #b
Is there any way to add an OR to the where clause using the SqlBuilder?
I think it's just a matter of changing the following in the SqlBuilder class to say OR instead of AND, but I wanted to confirm.
public SqlBuilder OrWhere(string sql, dynamic parameters = null)
{
AddClause("where", sql, parameters, " AND ", prefix: "WHERE ", postfix: "\n", IsInclusive: true);
return this;
}
Nevermind. I looked through the SqlBuilder code and found that if there is a mixture of Where and OrWhere, it will do the following:
Join all the AND clauses
Join all the OR clauses separately
Attach the OR clauses at the end of the AND clauses with an AND
If you don't have more than 1 OrWhere, then you won't see any OR.
I'll modify my query logic to take this into account
You have to change your query into:
var builder = new SqlBuilder();
var sql = builder.AddTemplate("select * from table /**where**/ ");
builder.OrWhere("a = #a", new { a = 1 })
.OrWhere("b = #b", new { b = 2 });
In case you want to try another alternative, DapperQueryBuilder may be easier to understand:
var query = cn.QueryBuilder($#"
SELECT *
FROM table
/**where**/
");
// by default multiple filters are combined with AND
query.FiltersType = Filters.FiltersType.OR;
int a = 1;
int b = 2;
query.Where($"a = {a}");
query.Where($"b = {b}");
var results = query.Query<YourPOCO>();
The output is fully parametrized SQL (WHERE a = #p0 OR b = #p1).
You don't have to manually manage the dictionary of parameters.
Disclaimer: I'm one of the authors of this library

Build dynamic Expression for search

I'm in trouble, can't figure out seems a very simple thing that in plain SQL can be done within 1 minute, it's been several hours so far. Here is the situation:
I have single field where user may enter as many words as he/she likes
I need to build Expression to find match
Let's say there are 3 fields in database: firstname, middlename, lastname
I need to split the search entry and compare against those 3 fields
I'm dealing with Silverlight RIA, EF
Once again search entry contains UNKNOWN number of words
Here under what I'm trying to accomplish, return type is mandatory:
public Expression<Func<MyEntity, bool>> GetSearchExpression(string text)
{
Expression<Func<MyEntity, bool>> result;
var keywords = text.Trim().Split(" ");
foreach(var keyword in keywords)
{
// TODO:
// check whether 'OR' is required (i.e. after second loop)
// (firstname = 'keyword'
// AND
// middlename = 'keyword'
// AND
// lastname = 'keyword')
// OR
// (firstname like '%keyword%'
// AND
// middlename like '%keyword%'
// AND
// lastname like '%keyword%')
}
return result;
}
Thanks in advance!
The simplest thing would be to use Joe Albahari's PredicateBuilder to do something like this:
var predicate = PredicateBuilder.False<MyEntity>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or (
p => p.FirstName.Contains (temp) &&
p.LastName.Contains (temp) &&
p.MiddleName.Contains (temp));
}
return predicate;
I left out the equality-checks because "Contains" (i.e. like '%...%') will cover that possibility anyway.
I do have to point out, though, that your conditions don't make any sense, from a business logic standpoint. Under what circumstances do you want to find someone whose first, last, and middle name all contain "John"? I suspect what you really want is something more like this:
var predicate = PredicateBuilder.True<MyEntity>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.And (
p => p.FirstName.Contains (temp) ||
p.LastName.Contains (temp) ||
p.MiddleName.Contains (temp));
}
return predicate;
One final note: Because PredicateBuilder requires you to call .AsExpandable() when you are creating your query, I don't know whether this will work for you. You might have to resort to building your own expressions, which can be somewhat tedious. This can get you started, though:
var pParam = Expression.Parameter(typeof(MyEntity), "p");
var predicate = Expression.Constant(true);
foreach (string keyword in keywords)
{
var keywordExpr = Expression.Constant(keyword);
// TODO: create an expression to invoke .FirstName getter
// TODO: create an expression to invoke string.Contains() method
//TODO: do the same for lastname and middlename
predicate = Expression.And(predicate,
Expression.Or(
Expression.Or(firstNameContainsKeyword,
middleNameContainsKeyword),
lastNameContainsKeyword));
}
return Expression.Lambda<Func<MyEntity, bool>>(predicate, pParam);

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