Multiple where clause using C# and SqlKata query engine - c#

I am working on a small Web API which will use SqlKata in the back end as the query engine to talk to SQL Server. In the documentation, SqlKata says the following in relation to multiple where clauses:
Multiple fields
If you want to filter your query against multiple fields, pass an object that represents col/values.
var query = new Query("Posts").Where(new {
Year = 2017 ,
CategoryId = 198 ,
IsPublished = true,
});
My intention is to use query strings to create the WHERE clauses in the back end, but I'm a bit stuck as to how to convert the Key/Value pairs of the query strings into properties of an object to use in the SqlKata query. The request will be highly dynamic in nature, so I can't really use a static object. I am wondering if anyone has any tips on how to construct an object to satisfy these requirements, being that the properties - both the property name and/or value - can be dynamic, as could the number of properties within the object. In my head I could imagine somehow converting the key / value pairs of the query strings into an object at run-time, but I'm stuck on how to achieve that.
I did try the following, but it seems each iteration through the loop the last key/value pair is replaced, so you end up with only the most recent WHERE clause being considered:
if (request.QueryStringParameters != null)
{
foreach (var element in request.QueryStringParameters)
{
CreateCloudWatchLog($"query string {element.Key} value {element.Value}", context, LogLevel.Trace, environmentLogLevel);
if (element.Key != "limit")
{
query = query.Where(element.Key, element.Value);
}
if (element.Key == "limit")
{
query = query.Limit(Convert.ToInt32(element.Value));
}
}
}
I have also tried this approach (query strings => json => dynamic object) but I get the error 'parameter count mismatch' (I am testing with one parameter passed in called 'storenumber' with a value of 399)
var json = JsonConvert.SerializeObject(request.QueryStringParameters, Formatting.Indented);
CreateCloudWatchLog($"Serialised query strings = {json}", context, LogLevel.Trace, environmentLogLevel);
var myobject = JsonConvert.DeserializeObject<dynamic>(json);
query = query.Where(myobject);
Debug logs:
[Trace] Serialised query strings =
{
"storenumber": "399"
}
[Trace] Finished converting JSON to object
[Error] Parameter count mismatch.

Looking at the documentation, it looks like you can use the basic where method which takes a property and a value. For example, assuming you have a dictionary of key value pairs, you could do this:
var filters = new Dictionary<string, object>
{
{ "Year", 2017 },
{ "CategoryId", 198 },
{ "IsPublished", true },
}
var query = new Query("Posts");
foreach(var filter in filters)
{
query = query.Where(filter.Key, filter.Value);
}

Related

Linq filter builder not accepting lambda for filter type IN

Basically what I'm trying to do is a deleteMany based on a list of items that I have using C# and linq for a mongoDB.
This is my code:
// List of elements to delete, read from the POST request
string jsonBoletines = await new StreamReader(req.Body).ReadToEndAsync();
// Convert the JSON into a list of "Boletin" objects, which is the type of objects I want //to delete from the collection
List <Boletin> boletinesToDelete = JsonConvert.DeserializeObject<List<Boletin>>(jsonBoletines);
// Create the filter using IN, I'm trying to delete using the _id
var filter = Builders<Boletin>.Filter.In(item => item._id, boletinesToDelete);
var results = collection.DeleteMany(filter);
My code will not compile because on the lambda inside the IN filter shows this error:
var filter = Builders<Boletin>.Filter.In(item => item._id, boletinesToDelete);
CS1660: Cannot convert lambda expression to type X because it is not a delegate type
Did some research and the error is supposed to be "This error occurs if you try to assign or otherwise convert an anonymous method block to a type which is not a delegate type". But I'm not sure I get it since I'm not that familiar with delegate type
Please help
From memory, I think an intersect may work better for this.
var filter = Builders<Boletin>.Intersect(boletinesToDelete);
boletinesToDelete should be a List<ObjectId> or a List<string> according to your item._id not a List<Boletin>.
db.collection.find({
_id: {
"$in": [
ObjectId("5a934e000102030405000000"),
ObjectId("5a934e000102030405000001")
]
}
})
mongoplayground
Not sure if it´s the best way to solve it, but it´s the way it worked for me
// read the contents of the POST body data into a string
string listBoletines = await new StreamReader(req.Body).ReadToEndAsync();
// listBoletines is a list of string IDs, so need to convert to ObjectId
//Fist create an array with each ID
string [] boletinesToDelete = listBoletines.Split(',');
//Create a second array of type ObjectId
ObjectId[] idsToDelete = new ObjectId[boletinesToDelete.Length];
//For each string on first array create it's corresponding ObjectId and insert on second array
for (int i=0; i<boletinesToDelete.Length; i++)
{
idsToDelete[i] = new ObjectId(boletinesToDelete[i]);
}
//Finally I an use that in the filter against a MongoDB _id
var filter = Builders<Boletin>.Filter.In("_id", idsToDelete);
var results = collection.DeleteMany(filter);

Expression to get LINQ with Contains to EF for SQL IN() where on entities child's property equals value

I have a simple need to filter all parents out of the returned collection where there is no match on a field, that is called by name from a string, doesn't match a value presented. What I am after is if parent object has child object, and that child objects property "foo"(called by string) doesn't or does equal a value bar, the parent object is filtered from the collection appropriately.
Here is my linq ef call
var field = "bar";
var values = new List<string>{"foo","fuYu"};
var dataPage = _aim_context.ae_s_bld_c.AsNoTracking();
var result = dataPage.Where(x =>
DbHelper.byPropertyContains(x.udfs, field, values)
);
// NOTE `udfs` is a ONE-to-ONE with `ae_s_bld_c`
What I am looking to see is something like the SQL of
SELECT [m].[id],[m.udfs].[bar],
FROM [dbo].[ae_s_bld_c] AS [m]
INNER JOIN [dbo].[ae_s_bld_c_udf] AS [m.udfs]
ON ([m].[multitenant_id] = [m.udfs].[multitenant_id])
WHERE ([m].[multitenant_id] = 1.0)
AND ([m.udfs].[bar] IN ('foo','fuYu')) --< Goal line
The way I have approached this was to get an expression set up to take the List<string> and make the SQL. I have read near 50 articles and SO posts, but have not figured out exactly why I am not getting this just yet as everyone seems to have different ideas, and most are not in line with dotnet core 2.1+ it seems.
Here is what I am sitting at currently after many many iterations. NOTE: it is a little different from what I am after as I am giving my current trail.
My current context linq try
//...
dataPage = dataPage.Where(DbHelper.byPropertyContains<ae_s_bld_c>("udfs", field, values));
//...
I think it would be better if it was like the first example I put up, but that was what I have landed on since I have had a time lining it up with x=>x.udfs, both as x=> funName(x.udfs) and x=> x.udfs.funName()
My static method to build the expression
public static class DbHelper
{
public static Expression<Func<T, bool>> byPropertyContains<T>(string node, string field, List<string> value) {
//trying to take parent item and get it's property by string name because
// doing the function in linq like x=>x.udfs was not working right
// but that is the prefered I think
var property_parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.PropertyOrField(property_parameter, node);
var selector_parameter = Expression.Parameter(property.Type, "y");
var selector = Expression.PropertyOrField(selector_parameter, field);
var methodInfo = typeof(List<string>).GetMethod("Contains", new Type[] {
typeof(string)
});
var list = Expression.Constant(value, typeof(List<string>));
var body = Expression.Call(methodInfo, list, selector);
return Expression.Lambda<Func<T, bool>>(body, selector_parameter);
}
}
Update
Per the request of #NetMage I have tried to work backwards with LINQpad. I think I am close but it is hard to tell with teh output. I am putting it up here for reference. To be clear, the property name of the child will be a string of the name. The best outcome is I could have a name like udfs.foo where I can test on any level if the values contain by string name, but really ok with it starting here,
var result = dataPage.Where(x =>
DbHelper.byPropertyContains(x.udfs, field, values)
);
Let start from here. You need an equivalent of something like this
var result = dataPage.Where(x => values.Contains(x.udfs.{field}));
where field is a string returning property dynamically specified by name.
In EF Core you don't even need to deal with building expresions by hand, because EF Core provides a special SQL translatable function for accessing simple properties by name called EF.Property.
With that method the solution is simple as that:
var result = dataPage
.Where(x => values.Contains(EF.Property<string>(x.udfs, field)));

Convert Dictionary to anonymous object in c#?

I have a web API I'm building in C#. This web API is used as a front end to query a SQL database. I am using SqlKata as the engine to perform the querying. When generating a WHERE clause, the SqlKata documentation states the following:
Multiple fields
If you want to filter your query against multiple fields, pass an object that represents col/values.
var query = new Query("Posts").Where(new {
Year = 2017 ,
CategoryId = 198 ,
IsPublished = true,
});
I want to be able to avoid hardcoding the WHERE clause, but rather base it on passed in querystrings. My idea was to add each querystring name and value to a Dictionary then somehow use the values of that Dictionary within the SqlKata .Where() clause. I thought maybe I could convert the Dictionary to the required anonymous object, but I can't get it to work. Any ideas folks on how to do this? So my url might be:
https://webapi.com.au/api?store=120&name=james
Dictionary:
store=120
name=james
Query:
var query = new query("Posts").Where(anonObject)
( anonObject would be {store=120,name=james} )
You don't need an anonymous object. Looking at the SqlKata docs, you can just build up the query by looping over the dictionary, something like this:
//Assuming the dictionary is something like Dictionary<string, object>
var query = new Query("Posts");
foreach(var element in dictionary)
{
query = query.Where(element.Key, element.Value);
}
var result = query.Get();
Note: Never even heard of SqlKata before so this is just from looking at the docs.

NHibernate: How to return list of scalar values (from one column) using CreateSQLQuery?

What is the best/cleanest method of calling a native SQL returning a list of scalar values (ints in my case) having ISession object?
I am trying to run the following but I am always getting some errors:
var query = _session.CreateSQLQuery("SELECT Id FROM SomeTable");
A. var ids = query.List<int>(); // <-- throws ArgumentNullException "Value cannot be null.\r\nParameter name: item"
B. var ids = query.List(); returns one element array with no valid information.
C. query.SetResultTransformer(NHibernate.Transform.Transformers.AliasToBean<int>());
var ids = query.List<int>(); // throws PropertyNotFoundException: "Could not find a setter for property 'Id' in class 'System.Int32'"
Is there a way to retrieve a list of ints without creating an entity class containing just one int property named Id?
When you call List from CreateSQLQuery you will get a instance of IList and it internally it is a List<object>. If you have null values on this result, you will not be able to convert to int because it is a value type. So, a solution is to iterate over the result and convert it when it is a valid integer. For sample:
var values = _session.CreateSQLQuery("SELECT Id FROM SomeTable").List();
var ids = new List<int>();
foreach (var item in values)
{
if (item != null)
ids.Add(Convert.ToInt32(item));
}
If this is a mapped table on the nhibernate scope, you could use LINQ to do this, for sample:
var ids = session.Query<SomeEntity>().Select(x => x.Id).ToList();
I know you are not using IQueryOver, but it is simpler, dynamic and cleaner than the way you are doing this now.
public IList<TReturn> GetValues<TEntity, TReturn>(IProjection column, Junction where, int top) where TEntity : BaseEntity
{
IQueryOver<TEntity> query = null;
if(where == null)
query = session.QueryOver<TEntity>().Select(column);
else
query = session.QueryOver<TEntity>().Select(column).Where(where);
IList<TReturn> instance = null;
if(top == 0)
instance = query.List<TReturn>();
else
instance = query.Take(top).List<TReturn>();
return instance;
}
TEntity in above code is entity that represent (mapped to) your table. Note that this is just to build the query. It will NOT return Entity.
TReturn is the return type. This can be any standard data type like int in your case.
IProjection column parameter is the name of the column you want to select.
Junction where parameter allows you to specify the filter on rows if any. To retrieve all rows, pass it null.
Following is the way you call it:
Junction where = Restrictions.Conjunction();
where.Add(Restrictions.Eq(..........));
IList<int> idList = GetValues<SomeTableEntity, int>(Projections.Property<SomeTableEntity>(x => x.Id), where, 0);
This way, you avoid writing hard-coded SQL query as string in your code. As you can see, this function can be used with any Entity (table) and any column.

ORMLite SqlList with Tuples

I'm having some troubles selecting a Tuple of Objects from my custom SQL query with ORMLite.
I have the following code:
var query = "select definition.*, timeslot.*, type.* from <blah blah>";
var defs = dbConnection.SqlList<Tuple<Definition, Timeslot, Type>>(query, new
{
/* query parameters */
});
The query itself is fine (I've tested it in SQL Management Studio).
The code above sets attributes only for the first item of the Tuple, leaving to the default state the others.
I've selected singularly each object and the result is correct (so no trouble during the conversion to POCO I guess).
Same thing goes if I use Select<Tuple<Definition, Timeslot, Type>> instead of SqlList.
I couldn't manage to try with MultiSelect since it appears to not take a string.
What is the correct way to select a Tuple in this manner?
I am working in C#.
Thanks in advance!
SelectMulti seems to be what you're looking for here.
From the documentation under the Selecting multiple columns across joined tables heading:
// Note: I'm making an assumption on your query here.
// Build the `q` object however it needs to be.
var q = db.From<Definition>()
.Join<Definition, Timeslot>()
.Join<Definition, Type>();
var results = db.SelectMulti<Definition, Timeslot, Type>(q);
foreach (var tuple in results)
{
var definition = tuple.Item1;
var timeslot = tuple.Item2;
var type = tuple.Item3;
}

Categories