Dynamic LINQ with Data Objects [duplicate] - linq

This question already has answers here:
How do I apply OrderBy on an IQueryable using a string column name within a generic extension method?
(8 answers)
Closed 2 years ago.
So I have been searching for a simple example to hopefully a simple problem.
I have a simple object a List of ListTest (List)
public class ListTest{
{
public string perName { get; set; }
public string perSex { get; set; }
public ListTest(string pName, string pSex)
{
this.perSex = pSex;
this.perName = pName;
}
}
I have loaded it with some data:
List<ListTest> tryIt = new List<ListTest>();
tryIt.Add(new ListTest("Karen", "F"));
tryIt.Add(new ListTest("Kate", "F"));
tryIt.Add(new ListTest("Glen", "M"));
tryIt.Add(new ListTest("Tiger", "M"));
tryIt.Add(new ListTest("Clementine", "F"));
tryIt.Add(new ListTest("Magnolia", "F"));
Now I want to query it using a Lambda Expression:
var things = tryIt
.Where(sex => (sex.perSex == "F"))
.OrderBy(sex => sex.perName);
But I want to do it dynamically, just in case I want to change my where to "perName".
I am able to create the Lambda Expression your Expressions, but I can't figure out how to take it across the goal line and actually assign it to a where clause and execute it.
IQueryable<ListTest> iq = tryIt.AsQueryable();
ParameterExpression pe = Expression.Parameter(typeof(ListTest), "Person");
Expression paEx = Expression.Property(pe, "perSex");
Expression right = Expression.Constant("F");
Expression eqEx = Expression.Equal(paEx, right);
Expression lE = Expression.Lambda<Func<ListTest, bool>>(eqEx, pe);
This should be a simple 4 or 5 line solution, but I can't find an easily decipherable solution example.
Should I use a MethodCallExpression or something down those lines?
Thanks,

You can try something like this:
IEnumerable<ListTest> things = tryIt;
if (someBooleanLogic)
things = things.Where(l => l.perSex == "F");
else
things = things.Where(l => l.perName == "Tiger");
things = things.OrderBy(sex => sex.perName);
List<ListTest> filteredAndSorted = things.ToList();
Edit:
Or, there's also the popular LINQ Dynamic Query Library, where you can form your query pretty much however you want (dynamically, of course).

Related

using linq to find if a text field contains any string in a list

im running this in asp.net core v3.1
my question is similar to this question:
How to use Linq to check if a list of strings contains any string in a list
with the specific question relating to the first answer such that
filterTags = ["abc", "cd", "efg"]
var results = db.People
.Where(p => filterTags.Any(tag => p.Tags.Contains(tag)));
so basically saying
give me results from the db of all People
who's Tags field contains any of the filterTags
where Tags = a big text field populated by a bunch of space-delimited tags
This seems straightforward (esp since this has been written before)
but i get an error back
System.InvalidOperationException: The LINQ expression 'DbSet
.Where(p => __filterTags_0
.Any(tag => p.Tags.Contains(tag)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync()
does anyone know what this means or what im doing wrong?
This is not possible with pure EF LINQ. You have to create helper which transforms your search list in Expression Tree.
public static class QueryExtensions
{
private static MethodInfo _containsMethodInfo = typeof(string).GetMethod("Contains")!;
public static IQueryable<T> FilterUsingContains<T>(this IQueryable<T> query, Expression<Func<T, string>> prop, IList<string> items)
{
if (items.Count == 0)
return query.Where(e => 1 == 2);
var param = prop.Parameters[0];
var predicate = items.Select(i =>
(Expression)Expression.Call(prop.Body, _containsMethodInfo, Expression.Constant(i, typeof(string))))
.Aggregate(Expression.OrElse);
var lambda = Expression.Lambda<Func<T, bool>>(predicate, param);
return query.Where(lambda);
}
}
Then you can use this extension in your queries
filterTags = ["abc", "cd", "efg"]
var results = db.People
.Where(p => p.Tags.AsQueryable().FilterUsingContains(t => t, filterTags).Any());
Here is a workaround for you:
using System.Linq;
string[] filterTags = {"abc", "cd", "efg"};
var results = db.People.Where(p => filterTags.Contains(p.Tags)).ToList();

Linq IEqualityComparer<string> Ignore Case [duplicate]

This question already has answers here:
Case insensitive 'Contains(string)'
(29 answers)
Closed 2 years ago.
I am sorting a list of elements:
var matchEle = listOfElements.Where(e => e.Properties().Any(p => p.Name.Contains("Key", Asking for IEqualityComparer))).First();
I am used to just going straight to a StringComparer, OrdinalIgnoreCase or CurrentCultureIgnoreCase, however when calling Contains() in this context, it is asking for an IEqualityComparer. I imagine because of the data structure/level. I saw an example of how to set up an IEqualityComparer such as
strEqualityComparer = new IEqualityComparer();
and defining the class for strEqualityComparer but I am not sure beyond that. Can someone help me get my linq statement to work with an ignore case?
Update:
Just so I'm clear here is an example of the data structure:
listOfElements = [element1, element2, etc..]
element1.Properties = ["Prop1", "Key1", "Prop2", "Key2", etc.]
I need to extract the elements which pass the filter if any of its properties has a value containing the keyword, in this case "Key" therefore it cannot be .Equals or IndexOf.
Update as per comment
Search string inside another string:
var matchEle = listOfElements
.Where(e => e.Properties().Any(p => p.Name.IndexOf("Key", System.StringComparison.OrdinalIgnoreCase) >= 0))
.First();
Old solutions
You have two options, that depends on Name type:
1 - Without IEqualityComparer, and if Name in Properties is a string. replace Contains by Equals like :
var matchEle = listOfElements
.Where(e => e.Properties().Any(p => p.Name.Equals("Key", StringComparison.OrdinalIgnoreCase)))
.First();
2 - With IEqualityComparer, and if Name in Properties is a list of string:
2.1 : Create a custom comparer, like:
public class StringIEqualityComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
return x.Equals(y, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(string obj)
{
return obj.GetHashCode();
}
}
2.2 : change little your query to :
var matchEle = listOfElements
.Where(e => e.Properties().Any(p => p.Name.Contains("Key", new StringIEqualityComparer())))
.First();
I hope this helps you.

How to use a string in the linq where clause?

I am trying to send a Linq query as a string to a method to be used in a where clause. Since IEnumerable wouldn't work for this, I have converted my IEnumerable to IQueryable and still it throws error. The following is the code:
public static void FilterData(string Query)
{
if((List<MemberMaintenanceData>)HttpContext.Current.Session["Allmembers"] != null)
{
//Get the IEnumerable object colection from session
var data = (List<MemberMaintenanceData>) HttpContext.Current.Session["Allmembers"];
//Convert it to IQueryable
IQueryable<MemberMaintenanceData> queryData = data.AsQueryable();
//This line doesn't compile!!
queryData = queryData.Where(Query);
HttpContext.Current.Session["Allmembers"] = queryData.AsEnumerable().ToList();
}
}
I intended passing "a => a.AccountId == 1000" as Query
There is a free (and open source) library, provided by Microsoft for parsing strings into Lambda expressions that can then be used in Linq queries. It also contains versions of the standard query operators such as Where() that take a string parameter. You can find it described in Scott Guthries blog post on Dynamic Linq.
For example, you can do queries like this (adapted from a snippet from the Scott guthrie link)
// imagine these have come from a drop down box or some other user input...
string thingToSelectBy = "City";
string citySelectedByUser = "London";
int minNumberOfOrders = 10;
string whereClause = String.Format("{0} = #0 and Orders.Count >= #1", thingToSelectBy);
var query = db.Customers
.Where(whereClause, citySelectedByUser, minNumberOfOrders)
.OrderBy("CompanyName")
.Select("new(CompanyName as Name, Phone");
The Where clause in thisw code snippet shows how you create a where clause using a parameterised string and then dynamically inject values for the parameters at run time, for example, based on user input. This works for parameters of any type.
In your example, the where clause would be
whereClause = "AccountId = 1000";
So in effect you would be doing something like
var newFilteredQueryData = queryData.Where("AccountId = 1000");
That link also contains the location where you can download the source code and a comprehensive document describing the dynamic query API and expression language.
Given a class such as:
public class foo
{
public int AccountID {get;set;}
}
You should be able to do something like this:
Expression<Func<foo, bool>> filter = f => f.AccountID == 1000;
And then pass that as your query. If it is really needed as a string you can do this:
filter.ToString();
//By Using this library
using System.Linq.Dynamic.Core;
InventoryList = Repository.GetAll(); // IQueryable
string filterString = "UnitPrice > 10 And Qty>100 OR Description.Contains("Dairy")";
var filteredGenericList = InventoryList.Where(filterString);

LINQ to Entities complex query

Is it possible ...??? I have 4 DropDownLists on my main page and the
user may select from any, all or some of
the DropDownLists. I am capturing their selection (or non-selection) using a SESSION
variable. What I would like to be able to do is pass the session
variable values to my Data Access Layer and build a WHERE clause
(maybe using StringBuilder) and then place that variable SOMEHOW into
my query expression. Is that possible??? Sorry, I'm a newbie. Thanks ~susan~
public class DLgetRestaurants
{
FVTCEntities db = new FVTCEntities();
public List<RESTAURANT> getRestaurants(string cuisineName, string priceName, string cityName)
[Build a string based on the values passed to the function]
{
var cuisineID = db.CUISINEs.First(s => s.CUISINE_NAME == cuisineName).CUISINE_ID;
List<RESTAURANT> result = (from RESTAURANT in db.RESTAURANTs.Include("CITY").Include("CUISINE").Include("Price")
where **[USE STRINGBUIDER EXPRSSION HERE]**
select RESTAURANT).ToList();
return result;
}
}
You can compose Where conditions which are linked by a logical AND relatively easy in LINQ extension method syntax:
var query = db.RESTAURANTs.Include("CITY").Include("CUISINE").Include("Price");
if (userHasSelectedInDDL1)
query = query.Where(r => r.PropertyForDDL1 == ValueFromDDL1);
if (userHasSelectedInDDL2)
query = query.Where(r => r.PropertyForDDL2 == ValueFromDDL2);
if (userHasSelectedInDDL3)
query = query.Where(r => r.PropertyForDDL3 == ValueFromDDL3);
if (userHasSelectedInDDL4)
query = query.Where(r => r.PropertyForDDL4 == ValueFromDDL4);
List<RESTAURANT> result = query.ToList();
For a much more flexible solution to build queries dynamically the Dynamic LINQ Library recommended by boca is probably the better choice.
I have done this in the past using the Dynamic Linq Library.

How to dynamically add OR operator to WHERE clause in LINQ

I have a variable size array of strings, and I am trying to programatically loop through the array and match all the rows in a table where the column "Tags" contains at least one of the strings in the array. Here is some pseudo code:
IQueryable<Songs> allSongMatches = musicDb.Songs; // all rows in the table
I can easily query this table filtering on a fixed set of strings, like this:
allSongMatches=allSongMatches.Where(SongsVar => SongsVar.Tags.Contains("foo1") || SongsVar.Tags.Contains("foo2") || SongsVar.Tags.Contains("foo3"));
However, this does not work (I get the following error: "A lambda expression with a statement body cannot be converted to an expression tree")
allSongMatches = allSongMatches.Where(SongsVar =>
{
bool retVal = false;
foreach(string str in strArray)
{
retVal = retVal || SongsVar.Tags.Contains(str);
}
return retVal;
});
Can anybody show me the correct strategy to accomplish this? I am still new to the world of LINQ :-)
You can use the PredicateBuilder class:
var searchPredicate = PredicateBuilder.False<Songs>();
foreach(string str in strArray)
{
var closureVariable = str; // See the link below for the reason
searchPredicate =
searchPredicate.Or(SongsVar => SongsVar.Tags.Contains(closureVariable));
}
var allSongMatches = db.Songs.Where(searchPredicate);
LinqToSql strange behaviour
I recently created an extension method for creating string searches that also allows for OR searches. Blogged about here
I also created it as a nuget package that you can install:
http://www.nuget.org/packages/NinjaNye.SearchExtensions/
Once installed you will be able to do the following
var result = db.Songs.Search(s => s.Tags, strArray);
If you want to create your own version to allow the above, you will need to do the following:
public static class QueryableExtensions
{
public static IQueryable<T> Search<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, params string[] searchTerms)
{
if (!searchTerms.Any())
{
return source;
}
Expression orExpression = null;
foreach (var searchTerm in searchTerms)
{
//Create expression to represent x.[property].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var containsExpression = BuildContainsExpression(stringProperty, searchTermExpression);
orExpression = BuildOrExpression(orExpression, containsExpression);
}
var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, stringProperty.Parameters);
return source.Where(completeExpression);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
if (existingExpression == null)
{
return expressionToAdd;
}
//Build 'OR' expression for each property
return Expression.OrElse(existingExpression, expressionToAdd);
}
}
Alternatively, take a look at the github project for NinjaNye.SearchExtensions as this has other options and has been refactored somewhat to allow other combinations
There is another, somewhat easier method that will accomplish this. ScottGu's blog details a dynamic linq library that I've found very helpful in the past. Essentially, it generates the query from a string you pass in. Here's a sample of the code you'd write:
Dim Northwind As New NorthwindDataContext
Dim query = Northwind.Products _
.Where("CategoryID=2 AND UnitPrice>3") _
.OrderBy("SupplierId")
Gridview1.DataSource = query
Gridview1.DataBind()
More info can be found at scottgu's blog here.
Either build an Expression<T> yourself, or look at a different route.
Assuming possibleTags is a collection of tags, you can make use of a closure and a join to find matches. This should find any songs with at least one tag in possibleTags:
allSongMatches = allSongMatches.Where(s => (select t from s.Tags
join tt from possibleTags
on t == tt
select t).Count() > 0)

Resources