I'm using EFCore in my project, and I'm attempting to execute a raw SQL query (because I'm targeting a database that I don't own and don't want to scaffold out the massive schema for it), passing in a parameter. Here is what I'm doing (although I've changed the query for protection):
var customerId = "testname";
using (var command = _dbContext.Database.GetDbConnection().CreateCommand())
{
command.CommandText = #"select * from customers where customerName = '#customerName'";
var customerIdParam = new SqlParameter("#customerName", customerId);
SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "#customerName";
parameter.Value = customerId;
parameter.DbType = System.Data.DbType.String;
parameter.Direction = System.Data.ParameterDirection.Input;
command.Parameters.Add(parameter);
await _dbContext.Database.OpenConnectionAsync();
using (var result = await command.ExecuteReaderAsync())
{
return result;
}
}
The problem is that the parameter doesn't seem to be passing in. If I hardcode the value for customerName that I'm sending in, it works just fine and returns rows, but if I pass it in as a parameter, result doesn't return any rows
The parameter must not be enclosed inside ', otherwise it is not detected as a parameter. Use this instead:
command.CommandText = #"select * from customers where customerName = #customerName";
Related
I'm trying to use the Azure DevOps .NET API to batch create WorkItems in a AzureDevOps repository, but when I submit the batch request, I'm getting back an error message: "VS403357: Work items in the batch are expected to be unique, but found work item with ID -1 in more than one request."
Here's my code:
public void ExecuteWorkItemMigration(int[] workItemIds, IProgress<ProgressResult> progress = null)
{
var wiql = "SELECT * FROM WorkItems";
var query = new Query(_workItemStore, wiql, workItemIds);
var workItemCollection = query.RunQuery();
string projectName = MainSettings.AzureDevOpsSettings.ProjectName;
List<WitBatchRequest> batchRequests = new List<WitBatchRequest>();
foreach (WorkItemTfs tfsWorkItem in workItemCollection)
{
JsonPatchDocument document = CreateJsonPatchDocument(tfsWorkItem);
string workItemType = GetWorkItemType(tfsWorkItem);
WitBatchRequest wibr = _azureDevopsWorkItemTrackingClient.CreateWorkItemBatchRequest(projectName, workItemType,
document, true, true);
batchRequests.Add(wibr);
}
List<WitBatchResponse> results = _azureDevopsWorkItemTrackingClient.ExecuteBatchRequest(batchRequests).Result;
}
private static JsonPatchDocument CreateJsonPatchDocument(WorkItemTfs tfsWorkItem, int id = -1)
{
var document = new JsonPatchDocument();
document.Add(
new JsonPatchOperation
{
Path = "/id",
Operation = Operation.Add,
Value = id
});
document.Add(
new JsonPatchOperation
{
Path = "/fields/System.Title",
Operation = Operation.Add,
Value = tfsWorkItem.Title
});
if (tfsWorkItem.Fields.Contains("ReproSteps"))
document.Add(
new JsonPatchOperation
{
Path = "/fields/Microsoft.VSTS.TCM.ReproSteps",
Operation = Operation.Add,
Value = tfsWorkItem.Fields["ReproSteps"].Value
});
}
Any suggestions about what I need to do to get this working properly?
I have tried submitting different unique ID's but it doesn't seem to prevent the error from happening.
You need to use unique negative ID's for creating the WorkItem ID.
Something like this:
public void ExecuteWorkItemMigration(int[] workItemIds, IProgress<ProgressResult> progress = null)
{
var wiql = "SELECT * FROM WorkItems";
var query = new Query(_workItemStore, wiql, workItemIds);
var workItemCollection = query.RunQuery();
string projectName = MainSettings.AzureDevOpsSettings.ProjectName;
List<WitBatchRequest> batchRequests = new List<WitBatchRequest>();
int id = -1;
foreach (WorkItemTfs tfsWorkItem in workItemCollection)
{
JsonPatchDocument document = CreateJsonPatchDocument(tfsWorkItem, id--);
string workItemType = GetWorkItemType(tfsWorkItem);
WitBatchRequest wibr = _azureDevopsWorkItemTrackingClient.CreateWorkItemBatchRequest(projectName, workItemType,
document, true, true);
batchRequests.Add(wibr);
}
List<WitBatchResponse> results = _azureDevopsWorkItemTrackingClient.ExecuteBatchRequest(batchRequests).Result;
}
I have been using below code to execute my SQL Query, which looks something like this
SELECT abc.... FROM .... (many joins).. WHERE userid =" + userId + " AND UserState = " +userState ...; (Other parameters)
Below is how I am running the query and returning datatable
using (var context = new DbContext())
{
DataTable dt= new DataTable();
var conn = context.Database.Connection;
var connectionState = conn.State;
try
{
if (connectionState != ConnectionState.Open)
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = buildveryLongQuery(userId,userState);
cmd.CommandType = CommandType.Text;
using (var reader = cmd.ExecuteReader())
{
if (reader.HasRows)
dt.Load(reader);
}
}
}
above method is working fine but it provides SQL injection. how can I parametrized it?
I tried below method:
Changed query to use #userId
IDbDataParameter personParam = cmd.CreateParameter();
personParam.DbType = DbType.Int32;
personParam.ParameterName = "#userId";
personParam.Value = userId;
but I am getting below error
Must declare the scalar variable \"#userId\
Below find a method that does not work. We fail on the line query.Select(...
Below that find a method with hard coded object property names which does work. But, this method is obviously not dynamic, nor flexible. There may be many properties of a Customer I may wish to search on.
The error string is at bottom. I get it that somehow the LINQ to Entity is unable to deal with conversion of GetValue to some sort of TSQL. Would anyone know of how I might code this up?
public List<Customer> GetForQuery(params Tuple<string, string>[] keyValuePairs) {
using (var db = new DBEntities()) {
var availableProperties = typeof(Customer).GetTypeInfo().DeclaredProperties.ToList();
var query = db.Customers.Select(c => c);
foreach (Tuple<string, string> pair in keyValuePairs) {
PropertyInfo pi = availableProperties.First(p => p.Name.Equals(pair.Item1));
if (pi == null)
continue;
query = query.Where(u => pi.GetValue(u, null).ToString().StartsWith(pair.Item2));
}
var results = query.Select(c => c).ToList();
return results;
}
}
How I might call the above:
CustomerController custController = new CustomerController();
List<Customer> results = custController.GetForQuery(Tuple.Create<string, string>("FName", "Bob" ));
The working fixed method:
public List<Customer> GetForQuery(string firstName = "", string lastName = "", string phoneNumber = "") {
using (var db = new DBEntities()) {
var query = db.Customers.Select(c => c);
if (firstName.HasContent())
query = query.Where(u => u.FName.StartsWith(firstName));
if (lastName.HasContent())
query = query.Where(u => u.LName.StartsWith(lastName));
if (phoneNumber.HasContent())
query = query.Where(u => u.EveningPhone.StartsWith(phoneNumber));
var results = query.Select(c => c).ToList();
return results;
}
}
ERROR:
LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object, System.Object[])' method, and this method cannot be translated into a store expression.
I'm not sure if there is a way to support this, but I'm having trouble getting Dapper to map string parameter values to the Postgresql citext data type as it seems to be using the text type.
In particular, I'm trying to call a function that takes in citext parameters - the error I get back is:
var c = ConnectionManager<T>.Open();
string sql = #"select * from ""dbo"".""MyFunction""(#schemaName, #tableName);";
var param = new
{
schemaName = schema,
tableName = table
};
string insecureSalt = c.QueryMultiple(sql, param).Read<string>().FirstOrDefault();
ConnectionManager<T>.Close(c);
Error: Npgsql.PostgresException: 42883: function dbo.MyFunction(text, text) does not exist.
The signature that would match is function dbo.MyFunction(citext, citext) so clearly it can't find it using the default mapping.
According to Npgsql - http://www.npgsql.org/doc/types.html I need to be able to specify NpgsqlDbType.Citext as the type but I can't find a way to do this using Dapper.
Solved thanks to answer from Shay, complete solution here:
var c = ConnectionManager<T>.Open();
string sql = #"select * from ""dbo"".""MyFunction""(#schemaName, #tableName);";
var param = new
{
schemaName = new CitextParameter(schema),
tableName = new CitextParameter(table)
};
string insecureSalt = c.QueryMultiple(sql, param).Read<string>().FirstOrDefault();
ConnectionManager<T>.Close(c);
public class CitextParameter : SqlMapper.ICustomQueryParameter
{
readonly string _value;
public CitextParameter(string value)
{
_value = value;
}
public void AddParameter(IDbCommand command, string name)
{
command.Parameters.Add(new NpgsqlParameter
{
ParameterName = name,
NpgsqlDbType = NpgsqlDbType.Citext,
Value = _value
});
}
}
You probably need to create create a CitextParameter which extends ICustomQueryParameter. This API allows you to pass an arbitrary DbParameter instance to Dapper - in this case it would be an instance of NpgsqlParameter with its NpgsqlDbType set to Citext.
Something like this should work:
class CitextParameter : SqlMapper.ICustomQueryParameter
{
readonly string _value;
public CitextParameter(string value)
{
_value = value;
}
public void AddParameter(IDbCommand command, string name)
{
command.Parameters.Add(new NpgsqlParameter
{
ParameterName = name,
NpgsqlDbType = NpgsqlDbType.Citext,
Value = _value
});
}
}
When you write the SQL query, you can cast the parameter value like cast(#param as citext).
in my case below worked correctly. (usr is a class object)
string sql = "select * from users where user_name = cast(#user_name as citext) and password = #password;";
IEnumerable<users> u = cnn.Query<users>(sql, usr);
In your case, you can change the query like below and see if that works
string sql = #"select * from ""dbo"".""MyFunction""(cast(#schemaName as citext), cast(#tableName as citext));";
In EF, Order By allows a column name to be used to eg OrderBy("Description")
I need to be able to do some thing similar with GroupBy
Other posts have solutions when the column type is known
var groupByExpressionGN2 = GetGroupByExpressionGuidNull<DebtWaiver>("PersonUID");
...
// in the query
.GroupBy(groupByExpression2)
// the Expression function
private static Expression<Func<TEntity,Guid?>> GetGroupByExpressionGuidNull<TEntity>(string property)
{
var item = Expression.Parameter(typeof(TEntity), "gb");
var itemProperty = Expression.PropertyOrField(item, property);
var lambda = Expression.Lambda<Func<TEntity, Guid?>>(itemProperty, item);
return lambda;
}
But my users may select 1 of any columns by which to group by
So how can I make the function above return an expression for group by
I have tried this:
public static Expression<Func<T, object>> GetMember<T>(string memberName)// where T : EntityObject
{
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
System.Reflection.PropertyInfo pi = typeof(T).GetProperty(memberName);
return (Expression<Func<T, object>>)Expression.Lambda<Func<T, object>>(Expression.Convert(Expression.Property(pe, pi), typeof(object)), pe);
}
but it produces : p =>Convert(p.PersonUID)
instead of:p =>p.PersonUID
Regards
GregJF
After a bit more testing (and a good night's sleep) I got the second method to work (Thanks to JA Rreyes )
My issue was that I was using this:
var groupByExpressionGN2 = GetGroupByExpressionGuidNull<DebtWaiver>("PersonUID");
...
// in the query
.GroupBy(groupByExpression2)
I should have being doing this:
var groupByExpression2 = GetMember<DebtWaiver>("PersonUID");
...
// in the query
.GroupBy(groupByExpression2.Compile())
You can use the second method in my original post( GetMember ), but I use this method('cos I like it!): Thanks: Taher Rahgooy
public static Expression<Func<T, object>> GetPropertySelector<T>(string propertyName)
{
var arg = Expression.Parameter(typeof(T), "gb");
var property = Expression.Property(arg, propertyName);
var conv = Expression.Convert(property, typeof(object));
var exp = Expression.Lambda<Func<T, object>>(conv, new ParameterExpression[] { arg });
return exp;
}
Regards
GregJF