Dynamic LINQ on a collection? - c#

I've a project which ask me to do such a BIG search engine but which is all dynamic. I mean I can have about 0 to 9 main "group" which have inside something like an infinite possibility of "where" with "OR" or "AND". First thing we think was to use Dynamic Linq which provide a good alternative to build dynamic query. All this using EF with an homemade wrapper.
Probleme :
I'm not able to access to a "Collection". I mean, I can easly access to a referenced object (Like Customer.State.StateName = "New-York" OR Custoemr.State.StateName = "Quebec" ) but I can't find a way to acces to something like : "Customer.Orders.OrderID = 2 OR Customer.Orders.OrderID = 3". I can easly figure out this its because its a collection, but how can I do this?
Please help me out!!
** Sorry for my english !!
Update
I'm not clear enought I think, sorry its because im french...
My problem its because nothing is static. Its a candidat search engine for a recruting compagny that place candidats into an enterprise. In a page where manager can search candidat, he can "parse" by : Domain(s) (Jobs), City(ies) or many other that user have filled up when he register. All this in format (if it were in SQL) :
[...] WHERE (domaine.domainID = 3 OR domaine.domainID = 5 OR domaine.domainID = 23) AND (cities.cityID = 4, cities.city = 32) [...]
So i can't do this with a normal LINQ format like :
Candidat.Domaines.Where(domain => domain.DomainID == 3 || domain.DomainID == 5 || domain.DomainID == 23);
Even the operator in the paretheses are dynamic ("AND" or "OR")! That why we trying to use Dynamic Linq because its a lot more flexible.
Hope its more easy to understand my problem ...
Update 2
Here's my method
private string BuildDomainsWhereClause() {
StringBuilder theWhere = new StringBuilder();
if (this.Domaines.NumberOfIDs > 0) {
theWhere.Append("( ");
theWhere.Append(string.Format("Domaines.Where( "));
foreach (int i in this.Domaines.ListOfIDs) {
if (this.Domaines.ListOfIDs.IndexOf(i) > 0) {
theWhere.Append(string.Format(" {0} ", this.DispoJours.AndOr == AndOrEnum.And ? "&&" : "||"));
}
theWhere.Append(string.Format("DomaineId == {0}", i));
}
theWhere.Append(" ))");
}
return theWhere.ToString();
}
It works great instead that it "Not return a boolean". So how should I?
Error : "Expression of type 'Boolean' expected".
At the end, it returns something like : "( Domaines.Where( DomaineId == 2 && DomaineId == 3 && DomaineId == 4 && DomaineId == 5 ))." which is added to my LINQ Query :
var queryWithWhere = from c in m_context.Candidats.Where(WHERE)
select c;
Dont forget that there's like 7 or 8 more "possible" added things to search in ... Any ideas?

What you need to do here, is build a LambdaExpression (more specifically an Expression<Func<T, bool>>). You cannot use a string. You can build a simple expression like this:
ParameterExpression p = Expression.Parameter(typeof(Domaine), "domaine");
Expression<Func<Domaine, bool>> wherePredicate =
Expression.Lambda<Func<Domaine, bool>>(
Expression.Or(
Expression.Equal(
Expression.Property(p, "DomainID"),
Expression.Constant(10)),
Expression.Equal(
Expression.Property(p, "DomainID"),
Expression.Constant(11))
), p);
i.e.,
domaine.DomainID = 10 || domaine.DomainID = 11
Not very readable if you need to do this by hand.
There's a sample of a fully operational expression parser that will actually do this for you based on a string in C# Samples for Visual Studio 2008 at MSDN Code Gallery, under DynamicQuery. (The LinqDataSource control uses a slightly modified version of this sample internally.)

Finaly i've got it exactly the way I want.
private string BuildDomainsWhereClause() {
StringBuilder theWhere = new StringBuilder();
if (this.Domains.NumberOfIDs > 0) {
theWhere.Append("( ");
foreach (int i in this.Domains.ListOfIDs) {
if (this.Domains.ListOfIDs.IndexOf(i) > 0) {
theWhere.Append(string.Format(" {0} ", this.Domains.AndOr == AndOrEnum.And ? "&&" : "||"));
}
theWhere.Append(string.Format("Domains.Any(IdDomaine== {0})", i));
}
theWhere.Append(" )");
}
return theWhere.ToString();
}
Which produce something like : "( DispoJours.Any(IdDispo == 3) && DispoJours.Any(IdDispo == 5) )".
All my other "Where builder" will do the same things with a "&&" between which give the correct result.
And later :
var queryWithWhere = from c in m_context.Candidats.Where(WHERE)
select c;
WHOOOHOOO !! Thanks folks. Were very usefull! Love this website!
Update
Don't forget that i use Dynamic Linq on this query. It's not a normal LINQ query.

Assuming that Customer.Orders returns a collection, this is exactly why you can't just call a property of it.
In order to use LINQ to get the Order you're looking for, you'd need to know the OrderID (Or some other property) in which case you can do:
Customer.Orders.Find(order => order.OrderID == 2);
Edit: To add the expression to find id 2 or 3 in this way:
Customer.Orders.FindAll(order => order.OrderID == 2 || order.OrderID == 3);

Do I understand that right, that both Customers is a collection and Orders is a collection while State (obviously) is a Property?
var q = from a in Customer
from b in a.Orders
where b.ID == 2
|| b.ID == 3
select b;
Would work I guess.
Edit:
I did partly something like that. It's been too long to be exactly sure how I did it, but I can tell you, that I was using
public static IQueryable<T> Where<T>(this IQueryable<T> source, string predicate, params object[] values);
From DynamicQueryable class.
this.CountrySitesObject.Sites.AsQueryable().Where(w.WhereQuery, w.WhereParameters)
(copied from my code).

If you step back and ask what does the customer want to do.
Filter bug information.
Why not export the data to excel or point excel to the SQL Table. It is not as much fun to build, but you would be done in a couple of hours, instead of days or weeks. :)

Related

How to build a Linq Query Dynamically

I have see a few old post but cannot figure out how to achieve this, Please help with an example.
I am running a query on a DataTable to group all the columns. The number columns will only be known as runtime hence I need to build the query dynamically.
var newGroup = from row in dataTable.AsEnumerable()
group row by new { ID = row.Field<string>("column1"), group1 = row.Field<string>("column2") };
I need to build the above query dynamically for n number of columns.
Please explain how can I build the ParameterExpression by looping over the columns list and build a Lambda expression.
In short: There are different ways on how to achieve this. The hard way is to build the combination of Func and Predicates by using the expressions. The more easier way is the utilization of library - LINQ Dynamic Query Library mentioned below:
Solution # 1: Here is a good start point to look - Building LINQ Queries at Runtime in C#
Solution # 2: you should be also able to do this by using Linq Dynamic Query, as it build for this purposes - Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library).
I've saw this question many times so I decided to create a blog but instead of manipulating the data from C#. I've done the heavy lefting from the SQL database level by utilizing dynamic SQL.
Here is the link. Hope this helps.
I had a situation where I needed to produce a LINQ query dynamically for both the left side and right side of the query. So in other words, Where("some property == some value"). I played around with PredicateBuilder from the guys at LINQPad, and tried a few other things, but in the end the LINQ Dynamic Query Library (System.Linq.Dynamic) made this task very simple.
Without going through all the details, what I have is a method that takes parameters for filtering and sorting items on a jqGrid on an MVC page. An object called QueryOptions holds various query settings. Data.ImportDataSearchView is the Entity Framework entity that is tied to a database view on the backend.
The filter expression is build up by calling:
options.FilterExpression += filterList.BuildFilterExpression<Data.ImportDataSearchView>();
Part of BuildFilterExpression is as follows:
public string BuildFilterExpression<T>()
{
var type = typeof(T);
var exp = string.Empty;
foreach (Filter filter in this)
{
var typeName = filter.DataType.ToLower();
// Skip if no values
if (!filter.Values.Any())
continue;
switch (typeName)
{
case "string":
// html decode string and escape single quotes
var stringVal = System.Web.HttpUtility.HtmlDecode(filter.Values[0]);
stringVal = stringVal.Replace("'", "''");
if (filter.Operator == Enums.FilterOperator.CONTAINS)
exp += string.Format("{0}.Trim().ToLower().Contains(\"{1}\")", filter.Attribute, stringVal.Trim().ToLower());
else if (filter.Operator == Enums.FilterOperator.DOES_NOT_EQUAL)
exp += string.Format("!{0}.ToLower().Equals(\"{1}\")", filter.Attribute, stringVal.ToLower());
else if (filter.Operator == Enums.FilterOperator.DOES_NOT_CONTAIN)
exp += string.Format("!{0}.Trim().ToLower().Contains(\"{1}\")", filter.Attribute, stringVal.Trim().ToLower());
else if (filter.Operator == Enums.FilterOperator.ENDS_WITH)
exp += string.Format("{0}.Trim().ToLower().EndsWith(\"{1}\")", filter.Attribute, stringVal.Trim().ToLower());
else if (filter.Operator == Enums.FilterOperator.EQUALS)
exp += string.Format("{0}.ToLower().Equals(\"{1}\")", filter.Attribute, stringVal.ToLower());
else if (filter.Operator == Enums.FilterOperator.STARTS_WITH)
exp += string.Format("{0}.Trim().ToLower().StartsWith(\"{1}\")", filter.Attribute, stringVal.Trim().ToLower());
break;
//case "select": -- for dropdowns
//case "datetime": -- for dates, etc. etc.
// add spaces around expression
exp = string.Format(" {0} ", exp);
// add and/or to expression
if (this.IndexOf(filter) != this.Count() - 1)
exp += string.Format(" {0} ", ExpressionType.ToLower() == "and" ? "&&" : "||");
}
return exp;
}
And then data was retrieved as follows, after building up the expression string:
options.OrderBy = string.IsNullOrEmpty(sortIndex) ? "TrackingId asc" : string.Format(" {0} {1} ", sortIndex, sortOrder);
var db = new Data.BmpDB();
var list = string.IsNullOrEmpty(options.FilterExpression)
? db.ImportDataSearchViews.OrderBy(options.OrderBy).ToList()
: db.ImportDataSearchViews.Where(options.FilterExpression).OrderBy(options.OrderBy).ToList();
The options.FilterExpression string below is an example of three search criteria fields pieced together by the method BuildFilterExpression into a nice, simple predicate string for the LINQ where clause:
"BmpName.Trim().ToLower().Contains(\"crops\") && DataProviderId.ToLower().Equals(\"123\") && StatusId == 1"

use "Like" Expression, LINQ to Entity

I spent long time trying to fix this but I'm not getting anywhere, so i help someone helps me.
I have a search form where I wish to use the Like % operator in the textbox.
Here is a snippet of my code:
var data = mg.DatabaseTable.Where(m => m.UserName.StartsWith(TextBoxUserID.Text) &&
m.Content.Like(%TextBoxBarcode.Text&) &&
m.Action.StartsWith(DropDownListStatus.Text) &&
m.Site.Contains(TextBoxSite.Text));
I would like it to be possible to use "%%" in the m.Content(textbox) or however it's made.
I am aware the use of StartsWith, EndWith and Contains. I would like to make it possible to choose how to query the search by using "like %"
You can use:
m.Content.StartsWith(TextBoxBarcode.Text);
for
like 'search%'
or:
m.Content.EndsWith(TextBoxBarcode.Text);
for
like '%search'
or:
m.Content.Contains(TextBoxBarcode.Text);
for
like '%search%'
If you want the user to be able to choose the type of search then you'll need to have a switch and then three different queries. Either replicate the query or perform the basic query once and then filter the results of that depending on the switch:
var data = mg.DatabaseTable.Where(m => m.UserName.StartsWith(TextBoxUserID.Text) &&
m.Action.StartsWith(DropDownListStatus.Text) &&
m.Site.Contains(TextBoxSite.Text));
if (searchMode == StartsWith)
{
return data.Where(m => m.Content.StartsWith(TextBoxBarcode.Text);
}
else if (searchMode == EndsWith)
{
return data.Where(m => m.Content.EndsWith(TextBoxBarcode.Text);
}
else
{
return data.Where(m => m.Content.Contains(TextBoxBarcode.Text);
}

Multiple where conditions in EF [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Conditional Linq Queries
Using Entity Framework 4.0
I have a search condition like this
There are four fields that allow the users to filter their search. The conditions are all AND. The result has to omit the corresponding filter if the textbox value is String.Empty or the dropdownlist value is All. Could do this in a Stored Procedure but I am unable to mimic that at all in a Linq2SQL/ Entity Framework scenario.
My question is this, how to omit IEnumerable.Where in the Linq according to some entered values?
You can chain your where clauses. You just need an IQueryable datasource.
var filteredData = _repository.GetAll();
//If your data source is IEnumerable, just add .AsQueryable() to make it IQueryable
if(keyWordTextBox.Text!="")
filteredData=filteredData.Where(m=>m.Keyword.Contains(keyWordTextBox.Text));
if(LocationDropDown.SelectedValue!="All")
filteredData=filteredData.Where(m=>m.Location==LocationDropDown.SelectedValue));
... etc....
Because it is IQueryable, the data is not fetched until you bind it so it only pulls the data you need.
Assuming that Location and Category are identified in your code by ids (id is the value attribute in the comboboxes items), you can do something similar to
function GetItems(string keyword, string consultant, int? locationId, int categoryId){
using(MyContextEntities context = new MyContextEntities()){
return context.Items.Where(item =>
(string.IsNullOrEmpty(keyword) || item.Text.Contains(keyword))
&& (string.IsNullOrEmpty(consultant) || item.Consultant.Contains(consultant))
&& (!locationId.HasValue || item.Location.Id == locationId.Value)
&& (!categoryId.HasValue || item.Category.Id == categoryId.Value)
);
}
}
Take a look at PredicateBuilder. It will allow you to do something like this:
IQueryable<??> SearchProducts (params string[] keywords)
{
var predicate = PredicateBuilder.True<??>();
foreach (string keyword in keywords)
{
string temp = keyword;
if(temp != String.Empty || temp != "All")
predicate = predicate.And(e => e.???.Contains (temp));
}
return dataContext.??.Where (predicate);
}
Note:
The flexible way to do this is to build up the where clause separately.
This article shows you how to do that. It takes a bit of work to initially set it up. But its worth it.
You can do something like this.
var abc = from al in myEntity.a
where (field == string.Empty ? al.field == string.Empty : al.field == field)
select new { al.field1, al.field2, al.field3 };
I think Skip While and Take While may help in your situation
http://msdn.microsoft.com/en-us/vcsharp/aa336757#SkipWhileSimple

Dynamically Building a List of Func<t,t> - then applying to a linq query

Not sure if this is the best approach, however here are my thoughts
I am using Entity Framework Data Model v 4.1 - i'm trying to build a where statement dynamically so that I dont have to query the db everytime, instead i can build a "list of" conditionals, then apply them all at once so the db is only queryed once as opposed to everytime i add my new conditional - if that makes sense...
here is what i have
List<Func<Order, bool>> orderFunctions = new List<Func<Order, bool>>();
if (this.LoggedInUser.UserId == 9)
{
Func<Order, bool> deleg = o => o.Status.Equals(OrderStatus.NewOrder);
orderFunctions.Add(deleg);
}
else if (this.LoggedInUser.UserId == 22)
{
Func<Order, bool> deleg = o => o.AssignedToUserId.Equals(22);
orderFunctions.Add(deleg);
}
List<Orders> orders = Entities.Orders.Where( Some How Apply my Order Functions List Here ).ToList();
I'm not sure if i'm even taking the right approach here - hopefully this makes sense - any guidance, sample code would be fantastic, i've having a heck of a time finding examples / tutorials for this online
You can do this just by calling Where multiple times:
IQueryable<Order> query = Entities.Orders;
if (this.LoggedInUser.UserId == 9)
{
query = query.Where(o => o.Status.Equals(OrderStatus.NewOrder));
}
else if (this.LoggedInUser.UserId == 22)
{
query = query.Where(o => o.AssignedToUserId.Equals(22));
}
List<Order> orders = query.ToList();
Until you start trying to retrieve the results, it won't contact the database.
If you really want to create a list, you'd need to create a List<Expression<Func<Order, bool>>> - they have to be expression trees rather than delegates, so that the Entity Framework can deal with them. Then you could just do:
foreach (var predicate in predicates)
{
query = query.Where(predicate);
}
Or you could use PredicateBuilder to build up a single expression tree.
Jon gave - of course - the answer how to do this better.
But for your original point, and why this won't work:
It is not dificult to apply the hole list (just do
x => orderFunctions.All(f => f(x))
) but you will not get any SQL-love from this - meaning you will get an error because EF cannot know what to do with the all (or whatever you will code there).
You would have to query all entries (with ToList, or ToArray) and after this it would work ... but without performance ;)

C# PredicateBuilder Entities: The parameter 'f' was not bound in the specified LINQ to Entities query expression

I needed to build a dynamic filter and I wanted to keep using entities. Because of this reason I wanted to use the PredicateBuilder from albahari.
I created the following code:
var invoerDatums = PredicateBuilder.True<OnderzoeksVragen>();
var inner = PredicateBuilder.False<OnderzoeksVragen>();
foreach (var filter in set.RapportInvoerFilter.ToList())
{
if(filter.IsDate)
{
var date = DateTime.Parse(filter.Waarde);
invoerDatums = invoerDatums.Or(o => o.Van >= date && o.Tot <= date);
}
else
{
string temp = filter.Waarde;
inner = inner.Or(o => o.OnderzoekType == temp);
}
}
invoerDatums = invoerDatums.And(inner);
var onderzoeksVragen = entities.OnderzoeksVragen
.AsExpandable()
.Where(invoerDatums)
.ToList();
When I ran the code there was only 1 filter which wasn't a date filter. So only the inner predicate was filled. When the predicate was executed I got the following error.
The parameter 'f' was not bound in the
specified LINQ to Entities query
expression.
While searching for an answer I found the following page. But this is already implemented in the LINQKit.
Does anyone else experienced this error and know how to solve it?
I ran across the same error, the issue seemed to be when I had predicates made with PredicateBuilder that were in turn made up of other predicates made with PredicateBuilder
e.g. (A OR B) AND (X OR Y) where one builder creates A OR B, one creates X OR Y and a third ANDs them together.
With just one level of predicates AsExpandable worked fine, when more than one level was introduced I got the same error.
I wasn't able to find any help but through some trial and error I was able to get things to work.
Every time I called a predicate I followed it with the Expand extension method.
Here is a bit of the code, cut down for simplicity:
public static IQueryable<Submission> AddOptionFilter(
this IQueryable<Submission> query,
IEnumerable<IGrouping<int, int>> options)
{
var predicate = options.Aggregate(
PredicateBuilder.False<Submission>(),
(accumulator, optionIds) => accumulator.Or(ConstructOptionMatchPredicate(optionIds).Expand()));
query = query.Where(predicate.Expand());
return query;
}
Query is an IQueryable which has already had AsExpandable called, ConstructOptionNotMatchPredicate returns an Expression.
Once we got past the error we were certainly able to build up complicated filters at runtime against the entity framework.
Edit:
Since people are still commenting on and up voting this I assume it is still useful so I am sharing another fix. Basically I have stopped using LinqKit and it's predicate builder in favour of this Universal Predicate Builder that has the same API but doesn't need Expand calls, well worth checking out.
I got this error and Mant101's explanation got me the answer, but you might be looking for a simpler example that causes the problem:
// This predicate is the 1st predicate builder
var predicate = PredicateBuilder.True<Widget>();
// and I am adding more predicates to it (all no problem here)
predicate = predicate.And(c => c.ColumnA == 1);
predicate = predicate.And(c => c.ColumnB > 32);
predicate = predicate.And(c => c.ColumnC == 73);
// Now I want to add another "AND" predicate which actually comprises
// of a whole list of sub-"OR" predicates
if(keywords.Length > 0)
{
// NOTICE: Here I am starting off a brand new 2nd predicate builder....
// (I'm not "AND"ing it to the existing one (yet))
var subpredicate = PredicateBuilder.False<Widget>();
foreach(string s in keywords)
{
string t = s; // s is part of enumerable so need to make a copy of it
subpredicate = subpredicate.Or(c => c.Name.Contains(t));
}
// This is the "gotcha" bit... ANDing the independent
// sub-predicate to the 1st one....
// If done like this, you will FAIL!
// predicate = predicate.And(subpredicate); // FAIL at runtime!
// To correct it, you must do this...
predicate = predicate.And(subpredicate.Expand()); // OK at runtime!
}
Hope this helps! :-)

Categories