Dynamic property + Linq - linq

I have a list of objects. I have a list of filter values. I need a count of the list of objects where a field/property (specified at run-time via variable) has a value found in the filter list. BTW there are other "filtered" properties as well but these property names are "fixed" and therefore are known at build-time.
Let's say the filter list has the following values:
Orange, Yellow, Blue
Let's say the object list has the following properties/values:
Name: Bike, Color: Red |
Name: Car, Color: Green |
Name: Box, Color: Yellow |
Name: Door, Color: Orange |
The "filter field" is string variable defined at run-time to be the "color" property/field.
The result should be 2 (box and door) because the other objects in the list do not have a "color" that is found in the filter list.
Here is what I have and it works but I am looking for a way to dynamically specify the "filter field" property name ("Field1") at run-time.
total = (from objects in ObjectsObservableCollection where ((object.InCity && cityList.Select(ma => ma.CityText).Contains(objects.CityLabel)) && FilterValuesList.Select(ff => ff.ToString().ToUpper()).Contains(objects.**Field1**.ToString().ToUpper())) select objects.ID).Count();
I could use a switch-case (with a select statement for each property) to determine which property is being used as the "filter field" at run-time (because all of the property names are known at run-time) but I really want to understand how to do it "the right way".
I think Expressions is the right way to go here but I can't wrap my head around the syntax. If it was 1 value I was searching (instead of a list of values), I think I could do it.
Can anyone give me a hint, maybe point to an article or tutorial or perhaps explain what I need to use to accomplish this?

If you already have a fixed set of fields you will want to test, and assuming they are all string fields, you can create a Dictionary of accessor lambdas that can be looked up by field name:
var FilterValues = FilterValueList.Select(s => s.ToLowerInvariant()).ToHashSet();
var TransportAccessorsDict = new[] {
new { FieldName = "Color", Accessor = (Func<Transport,String>)(t => t.Color) },
new { FieldName = "Name", Accessor = (Func<Transport,String>)(t => t.Name) },
}
.ToDictionary(fa => fa.FieldName, fa => fa.Accessor);
var getFilterField = TransportAccessorsDict[FilterField];
var total = (from objects in ObjectsObservableCollection
where FilterValues.Contains(getFilterField(objects).ToLowerInvariant())
select objects.ID
).Count();
This should be reasonably performant, though if performance is a big consideration, you should probably be converting the FilterValuesList to an uppercase (I think lowercase is preferred though both are bad, a culture aware string insensitive contains would be best) HashSet before using it in the LINQ query.
I removed the ToString calls as they should be redundant, otherwise you have bigger (type) issues.

Related

.net core when I read from appsettings, the sort is changed. Why?

I have this config in appsettings.json:
"CategoriesTypes": [ "Country", "State", "Semester" ],
in the code I read values like:
var array = Configuration.GetSection("CategoriesTypes").AsEnumerable() .Where(o => !string.IsNullOrEmpty(o.Value)).Select(o => o.Value).ToArray()
// output of foreach loop:
"Semester", "State", "Country"
Why The sort is changed?
IEnumerable do not pretend to keep items in order, unless you ask for. And as described in the docs
Methods that are used in a query that returns a sequence of values do not consume the target data until the query object is enumerated.
When you iterate through an IEnumerable it is just navigating to the previous/next item without knowing it in advance. So IEnumerable do not keep items sorted, because sorting requires to enumerate through the whole items in the list.
If you need to sort the list just use Linq extensions OrderBy or OrdrByDescending which will return IOrderedEnumerable<T>.

How to update item conditionally with branch in RethinkDB

I am trying to do simple upsert to the array field based on branch condition. However branch does not accept a reql expression as argument and I get error Expected type SELECTION but found DATUM.
This is probably some obvious thing I've missed, however I can't find any working example anywhere.
Sample source:
var userId = 'userId';
var itemId = 'itemId';
r.db('db').table('items').get(itemId).do(function(item) {
return item('elements').default([]).contains(function (element) {
return element('userId').eq(userId);
}).branch(
r.expr("Element already exist"),
//Error: Expected type SELECTION but found DATUM
item.update({
elements: item('elements').default([]).append({
userId: 'userId'
})
})
)
})
The problem here is that item is a datum, not a selection. This happens because you used r.do. The variable doesn't retain information about where the object originally came from.
A solution that might seem to work would be to write a new r.db('db').table('items').get(itemId) expression. The problem with that option is the behavior isn't atomic -- two different queries might append the same element to the 'elements' array. Instead you should write your query in the form r.db('db').table('items').get(itemId).update(function(item) { return <something>;) so that the update gets applied atomically.

Rails 4 and Mongoid: programmatically build query to search for different conditions on the same field

I'm building a advanced search functionality and, thanks to the help of some ruby fellows on SO, I've been already able to combine AND and OR conditions programmatically on different fields of the same class.
I ended up writing something similar to the accepted answer mentioned above, which I report here:
query = criteria.each_with_object({}) do |(field, values), query|
field = field.in if(values.is_a?(Array))
query[field] = values
end
MyClass.where(query)
Now, what might happen is that someone wants to search on a certain field with multiple criteria, something like:
"all the users where names contains 'abc' but not contains 'def'"
How would you write the query above?
Please note that I already have the regexes to do what I want to (see below), my question is mainly on how to combine them together.
#contains
Regex.new('.*' + val + '.*')
#not contains
Regex.new('^((?!'+ val +').)*$')
Thanks for your time!
* UPDATE *
I was playing with the console and this is working:
MyClass.where(name: /.*abc.*/).and(name: /^((?!def).)*$/)
My question remains: how do I do that programmatically? I shouldn't end up with more than two conditions on the same field but it's something I can't be sure of.
You could use an :$and operator to combine the individual queries:
MyClass.where(:$and => [
{ name: /.*abc.*/ },
{ name: /^((?!def).)*$/ }
])
That would change the overall query builder to something like this:
components = criteria.map do |field, value|
field = field.in if(value.is_a?(Array))
{ field => value }
end
query = components.length > 1 ? { :$and => components } : components.first
You build a list of the individual components and then, at the end, either combine them with :$and or, if there aren't enough components for :$and, just unwrap the single component and call that your query.

LINQ to EF - Find records where string property of a child collection at least partially matches all records in a list of strings

I am currently writing a web-based 'recipe' application using LINQ and Entity Framework 5.0. I've been struggling with this query for awhile, so any help is much appreciated!
There will be a search function where the users can enter a list of ingredients that they want the recipe results to match. I need to find all recipes where the associated ingredient collection (name property) contains the text of every record in a list of strings (the user search terms). For example, consider the following two recipes:
Tomato Sauce: Ingredients 'crushed tomatoes', 'basil', 'olive oil'
Tomato Soup: Ingredients 'tomato paste', 'milk', 'herbs
If the user used the search terms 'tomato' and 'oil' it would return the tomato sauce but not the tomato soup.
var allRecipes = context.Recipes
.Include(recipeCategory => recipeCategory.Category)
.Include(recipeUser => recipeUser.User);
IQueryable<Recipe> r =
from recipe in allRecipes
let ingredientNames =
(from ingredient in recipe.Ingredients
select ingredient.IngredientName)
from i in ingredientNames
let ingredientsToSearch = i where ingredientList.Contains(i)
where ingredientsToSearch.Count() == ingredientList.Count()
select recipe;
I've also tried:
var list = context.Ingredients.Include(ingredient => ingredient.Recipe)
.Where(il=>ingredientList.All(x=>il.IngredientName.Contains(x)))
.GroupBy(recipe=>recipe.Recipe).AsQueryable();
Thank you for your help!
Just off the top of my head i would go for something like this
public IEnumerable<Recipe> SearchByIngredients(params string[] ingredients)
{
var recipes = context.Recipes
.Include(recipeCategory => recipeCategory.Category)
.Include(recipeUser => recipeUser.User);
foreach(var ingredient in ingredients)
{
recipes = recipes.Where(r=>r.Ingredients.Any(i=>i.IngredientName.Contains(ingredient)));
}
//Finialise the queriable
return recipes.AsEnumerable();
}
You can then call it using:
SearchByIngredients("tomatoes", "oil");
or
var ingredients = new string[]{"tomatoes", "oil"};
SearchByIngredients(ingredients );
What this is going to do is attach where clauses to the queriable recipes for each of your search terms. Multiple where clauses are treated as ANDs in SQL (which is what you want here anyway). Linq is quite nice in the way that we can do this, at then end of the function we finalise the queriable essentially saying all that stuff that we just did can get turned into a single query back to the DB.
My only other note would be you really want to be indexing/full text indexing the Ingredient name column or this wont scale terribly well.

Imroving/Modifying LINQ query

I already have a variable containing some groups. I generated that using the following LINQ query:
var historyGroups = from payee in list
group payee by payee.Payee.Name into groups
orderby groups.Key
select new {PayeeName = groups.Key, List = groups };
Now my historyGroups variable can contain many groups. Each of those groups has a key which is a string and Results View is sorted according to that. Now inside each of those groups there is a List corresponding to the key. Inside that List there are elements and each one those element is an object of a particular type. One of it's fields is of type System.DateTime. I want to sort this internal List by date.
Can anyone help with this? May be modify the above query or a new query on variable historyGroups.
Thanks
It is not clear to me what you want to sort on (the payee type definition is missing as well)
var historyGroups = from payee in list
group payee by payee.Payee.Name into groups
orderby groups.Key
select new {
PayeeName = groups.Key,
List = groups.OrderBy(payee2 => payee2.SomeDateTimeField)
};
Is most straightforward.
If you really want to sort only by date (and not time), use SomeDateTimeField.Date.
Inside that List there are elements and each one those element is an object of a particular type. One of it's fields is of type System.DateTime
This leads me to maybe(?) suspect
List = groups.OrderBy(payee2 => payee2.ParticularTypedElement.DateTimeField)
Or perhaps even
List = groups.OrderBy(payee2 => payee2.ObjectsOfParticularType
.OfType<DateTime>()
.FirstOrDefault()
)
I hope next time you can clarfy the question a bit better, so we don't have to guess that much (and come up with a confusing answer)

Resources