I found the following comment on the Dapper .NET project home page.
Dapper supports varchar params, if you are executing a where clause on a varchar column using a param be sure to pass it in this way:
Query<Thing>("select * from Thing where Name = #Name", new {Name =
new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true });
On Sql Server it is crucial to use the unicode when querying unicode and ansi when querying non unicode
I'm evaluating Dapper for use with a legacy database (SQL Server 2008), with lots of stored procedures with varchar parameters, and I'm a little confused by this restriction.
With hand-crafted ADO.NET code, I'd use the following for the above query:
new SqlParameter("#Name", "abcde")
without specifying whether it's unicode or not, nor the length.
Why do I need this verbose DbString syntax with Dapper, specifying the column length, IsFixedLength and IsAnsi?
Why IsFixedLength = true for a varchar column (I'd expect it to be true for a char or nchar column)?
Do I have to use DbString like this for stored procedure parameters?
I was expecting Dapper to make my DAL code more concise, but this seems to be making it more verbose for varchar parameters.
UPDATE
I've researched a bit further, to try to understand why Dapper would have this varchar restriction, which I don't appear to have in my hand-crafted code, where I would normally create an input parameter as follows:
var parameter = factory.CreateParameter(); // Factory is a DbProviderFactory
parameter.Name = ...;
parameter.Value = ...;
and usually leave the provider to infer the DbType using its own rules, unless I specifically want to coerce it.
Looking at Dapper's DynamicParameters class, it has a method AddParameters which creates parameters as follows:
var dbType = param.DbType; // Get dbType and value
var val = param.Value; // from
...
// Coerce dbType to a non-null value if val is not null !!!!!
if (dbType == null && val != null) dbType = SqlMapper.LookupDbType(val.GetType(),name);
...
var p = command.CreateParameter();
...
if (dbType != null)
{
p.DbType = dbType.Value;
}
I.e. it explicitly coerces IDataParameter.DbType to a value it looks up with its own algorithm, rather than leaving the provider to use its own rules.
Is there a good reason for this? It seems wrong for me, particularly in the light of the comment about Dapper's support for varchar parameters.
You need this syntax when working with ODBC.
You would need to define a CHAR(30) field as a DbString in c# for Dapper and also set the length (30) and ansi (true) values to prevent Dapper from assuming the string was a text/blob type. Otherwise you will likely receive the error: "Illegal attempt to convert Text/Byte blob type".
I was getting this error using ODBC to connect to Informix until I defined my param as a DbString() and set the length and ansi values.
More info here.
var param = new { Varchar1 = "", Varchar2 = "" };
db.Query("SP", param, commandType:CommandType.StoredProcedure);
Related
I'm writing a custom M Language (PowerQuery in Excel) function to query a RESTful interface. This interface has a large number of optional parameters.
Starting with a simple case- I handle an optional limit passed as a simple (primitive) value as follows-
/*
* RESTful API Get from the named endpoint
*/
(endpoint as text, optional limit) =>
let
// query limit
// If limit is supplied as a number, it will be converted to text
// If limit is not supplied it will be set to the value "1000"
limit = if limit <> null then Text.From(limit) else "1000",
As the full API has many paramaters I wanted to use a Record to pass them to the function, but then I realised I don't know how to persuade M to write the default values into the parameter record.
I tried a couple of options.
Direct access-
(endpoint as text, optional params as record) =>
let
params[limit] = if (params[limit] = null) then "1000",
the result is a syntax error-'Token equal expected'
Merging the new value of limit as a Record with "&"
(endpoint as text, optional params as record) =>
let
params = params & if params[limit] = null then [limit = "1000"] else [],
result syntax error-'Token Literal expected'
I'm clearly missing something about the syntax rules for let statements, I know I need a variable = value assignment, and it looks as if putting anything other than a plain variable name on the LHS to write elements inside a structured value is not allowed, but i'm not sure how to acieve this otherwise?
Not sure exactly what you want here, but to create a List of Records where some Records have a default parameter and others do not, you could try something like:
(newParams as record) =>
let
default = [limit=1000, param2=2, param3=3],
final = Record.Combine({default, newParams})
in
final
With regard to Record.Combine, the beauty is that the right hand record will override the left hand record if both are present; and it will just add to it if nothing is present.
So something like:
let
Source = [limit=400, param3="x", param7=246],
conv = fnParams(Source)
in
conv
=>
Depending on the required format of your output string, you can build it using List.Accumulate. eg:
let
Source = [limit=400, param3="x", param7=246],
conv = fnParams(Source),
list = List.Accumulate(List.Zip({Record.FieldNames(conv), Record.ToList(conv)}), "",
(state,current) =>state & "&" & current{0} & "=" & Text.From(current{1}) )
in
list
=> &limit=400¶m2=2¶m3=x¶m7=246
The view definition emits a string field from the document as a key. The field value can be all numeric or alphanumeric. Query using key with all numeric value does not return any row but alphanumeric key returns data.
On server web console and rest api, I could see the row so view is getting updated properly and hence leaning to believe that issue is with java sdk client.
Below is the code I use to query.
CouchbaseClient couchBaseDAO; // = initialize client.
String corelationId = "12345678";
Query query = new Query();
query.setKey(corelationId);
ViewResponse result = couchBaseDAO.query(queryConfig, query);
JSONArray jsonArray = new JSONArray();
if(result != null){
for(ViewRow row: result){
jsonArray.put(row.getValue());
}
}
return jsonArray.toString();
Map:
function(doc,meta) {
if(doc!=null && doc.requestData!=null) {
emit(doc.requestData.corelationId, [doc.request.id, doc.status]);
}
}
If I changed key to alphanumeric, it works.
String corelationId = "ab-12-09-a-123";
Java HotSpot 7.
Couchbase java sdk 1.4.7
Couchbase Server 3.0.3
Solution
Based on the information given in answer below, below are two options you have
Option 1 Server side map change
If you are building a new map than go for it. Harmonize your key to become always string emit("" + doc.requestData.corelationId, ...);
If your view already exists then all your existing documents will not change right away.
Option 2 Client side change
If you are like me where option 1 is not possible, go for harmonizing your key in your code. It overcomes's skd's logic to treat it as numeric.
corelationId = StringUtils.isNumeric(corelationId)?"\""+corelationId+"\"":corelationId;
Your view emits the corelationId as it is, in its original type. You said that in the documents it was alternating between a numerical value and a string.
If you pass the key to the SDK as a Long it will work.
(I suspect that in the web ui you naturally typed in 12345678 in the key field and not "12345678", so you did the correct equivalent of using a Long in the web UI)
If you cannot know the correct type to use for each key you search, harmonize the key type in the map function so that you know always to use strings:
emit("" + doc.requestData.corelationId, ...);
After reading "Asynchronous queries with the Java driver" article in the datastax blog, I was trying to implement a solution similar to the one in the section called - 'Case study: multi-partition query, a.k.a. “client-side SELECT...IN“'.
I currently have code that looks something like this:
public Future<List<ResultSet>> executeMultipleAsync(final BoundStatement statement, final Object... partitionKeys) {
List<Future<ResultSet>> futures = Lists.newArrayListWithExpectedSize(partitionKeys.length);
for (Object partitionKey : partitionKeys) {
Statement bs = statement.bind(partitionKey);
futures.add(executeWithRetry(bs));
}
return Futures.successfulAsList(futures);
}
But, I'd like to improve on that. In the cql query this BoundStatement holds, I'd like to have something that looks like this:
SELECT * FROM <column_family_name> WHERE <param1> = :p1_name AND param2 = :p2_name AND <partiotion_key_name> = ?;
I'd like the clients of this method to give me a BoundStatement with an already bound parameters (two parameters in this case) and a list of partition keys. In this case, all I need to do, is bind the partition keys and execute the queries. Unfortunately, when I bind the key to this statement I fail with an error - com.datastax.driver.core.exceptions.InvalidTypeException: Invalid type for value 0 of CQL type varchar, expecting class java.lang.String but class java.lang.Long provided. The problem is, that I try to bind the key to the first parameter and not the last. Which is a string and not a long.
I can solve this by either giving the partition parameter a name but then I'd have to get the name via method parameters, or by specifying it's index which again will require an additional method parameter. Either way, if I use the name or the index I have to bind it with a specific type. For instance: bs.setLong("<key_name>", partitionKey);. For some reason, I can't leave it to the BoundStatement to interpret the type of the last parameter.
I'd like to avoid passing the parameter name explicitly and bypass the type problem. Is there anything that can be done?
Thanks!
I've posted the same question in 'DataStax Java Driver for Apache Cassandra User Mailing List' and got an answer saying the functionality that I'm missing may be added in the next version (2.2) of the datastax java driver.
In JAVA-721 (to be introduced in 2.2) we are tentatively planning on
adding the following methods with the signature to BoundStatement:
public BoundStatement setObject(int i, V v) public
BoundStatement setObject(String name, V v)
and
You can emulate setObject in 2.1:
void setObject(BoundStatement bs, int position, Object object,
ProtocolVersion protocolVersion) {
DataType type = bs.preparedStatement().getVariables().getType(position);
ByteBuffer buffer = type.serialize(object, protocolVersion);
bs.setBytesUnsafe(position, buffer);
}
To avoid passing the parameter name, one thing you could do is look
for a position that isn't bound yet:
int findUnsetPosition(BoundStatement bs) {
int size = bs.preparedStatement().getVariables().size();
for (int i = 0; i < size; i++)
if (!bs.isSet(i))
return i;
throw new IllegalArgumentException("found no unset position");
}
I don't recommend it though, because it's ugly and unpredictable if
the user forgot to bind one of the non-PK variables.
The way I would do it is require the user to pass a callback that sets
the PK:
interface PKBinder<T> {
void bind(BoundStatement bs, T pk);
}
public <T> Future<List<ResultSet>> executeMultipleAsync(final BoundStatement statement, PKBinder<T> pkBinder, final T...
partitionKeys)
As a bonus, this will also work with composite partition keys.
This thread gives a number of ways to provide a parameter to a stored procedure.
None of them are working for me.
This works:
this.PayrollContext.Database.SqlQuery<CSRRateEntity>("ord_getCSRRate #csr_num = '4745', #ord_pay_period_id = 784").ToList();
This does not:
return this.PayrollContext.Database.SqlQuery<CSRRateEntity>("Exec ord_getCSRRate #csr_num, #ord_pay_period_id",
new SqlParameter("csr_num", "4745"),
new SqlParameter("ord_pay_period_id", 784)
).ToList();
The Error message is that parameter is not supplied.
Have tried all the variations I can think of and still get that same error message.
This is using Code First, so no import is required. The SP is found, it is just missing the parameters.
Did you import the stored procedure into the edmx?
In the implementation that we have for the stored procedure it creates parameters like this:
var inputIdParameter = inputId.HasValue ?
new ObjectParameter("InputId", inputId) :
new ObjectParameter("InputId", typeof(int));
The result of the stored procedure function is given like this
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction<T>("[SP_NAME]", inputIdParameter);
The solution turned out to be to change the order of the parameters in the stored procedure so that optional parameters come after the required parameters (like in c#).
Works:
ALTER procedure [dbo].[ord_GetCSRRate]#ord_pay_period_id int,#csr_num varchar(10) = null, #csr_id int = null, #update_csr_pay_flag bit = 0
Does not work:
ALTER procedure [dbo].[ord_GetCSRRate]#csr_num varchar(10) = null, #ord_pay_period_id int, #csr_id int = null, #update_csr_pay_flag bit = 0, #recursion_flag bit = 0
I have a list AllIDs:
List<IAddress> AllIDs = new List<IAddress>();
I want to do substring operation on a member field AddressId based on a character "_".
I am using below LINQ query but getting compilation error:
AllIDs= AllIDs.Where(s => s.AddressId.Length >= s.AddressId.IndexOf("_"))
.Select(s => s.AddressId.Substring(s.AddressId.IndexOf("_")))
.ToList();
Error:
Cannot implicitly convert type 'System.Collections.Generic.List<string>' to 'System.Collections.Generic.List<MyCompany.Common.Users.IAddress>'
AllIDs is a list of IAddress but you are selecting a string. The compiler is complaining it cannot convert a List<string> to a List<IAddress>. Did you mean the following instead?
var substrings = AllIDs.Where(...).Select(...).ToList();
If you want to put them back into Address objects (assuming you have an Address class in addition to your IAddress interface), you can do something like this (assuming the constructor for Address is in place):
AllIDs = AllIDs.Where(...).Select(new Address(s.AddressID.Substring(s.AddressID.IndexOf("_")))).ToList();
You should also look at using query syntax for LINQ instead of method syntax, it can clean up and improve the readability of a lot of queries like this. Your original (unmodified) query is roughly equivalent to this:
var substrings = from a in AllIDs
let id = a.AddressId
let idx = id.IndexOf("_")
where id.Length >= idx
select id.Substring(idx);
Though this is really just a style thing, and this compiles to the same thing as the original. One slight difference is that you only have to call String.IndexOf() one per entry, instead of twice per entry. let is your friend.
Maybe this?
var boundable =
from s id in AllIDs
where s.AddressId.Length >= s.AddressId.IndexOf("_")
select new { AddressId = s.AddressId.Substring(s.AddressId.IndexOf("_")) };
boundable = boundable.ToList();