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"
Related
I'm trying to build a query using Dynamic Linq and a where string statement calculated by myself. For example:
List<Publication> results = new List<Publication>();
// Just an example, previously calculated dynamically
string filterQuery = "(Id = 1 and Number = 2)";
IQueryable<Publication> query = db.Publications.Include(i => i.Product);
query = query.Where(filterQuery);
results = query.OrderBy(orderQuery).ToList();
This is working great and I get a List of Publications with Products. Now... the question is. How can I make a string statement to get results based on a relation to Product using Dynamic Linq and a string statement?
Something like:
string filterQuery = "(Id = 1 and Number = 2 and Products.Id = 1)"
After a lot of research and trying things, this is an easy and friendly way using the same Dynamic Linq library:
List<Publication> results = new List<Publication>();
// Just an example, previously calculated dynamically
string filterQuery = "(Id = 1 and Number = 2)";
string filterQueryChildren = "Products.Any(Id == 1)"
IQueryable<Publication> query = db.Publications.Include(i => i.Product).Where(filterQueryChildren);
query = query.Where(filterQuery);
results = query.OrderBy(orderQuery).ToList();
I am not sure why you're doing this, but the following should work:
List<Publication> results = new List<Publication>();
// Just an example, previously calculated dynamically
string filterQuery1 = "(Id = 1)"
string filterQuery2 = "(Id = 1 and Number = 2)";
IQueryable<Publication> query = db.Publications.
Where(filterQuery1).
Include(i => i.Product);
query = query.Where(filterQuery2);
results = query.OrderBy(orderQuery).ToList();
To make dynamic LINQ query, you need a library that support it or doing it yourself with Expression Tree
See: Entity Framework Dynamic Query Library
Disclaimer: I'm the owner of the project Eval-Expression.NET
This library allows you to evaluate, compile, and execute code at runtime.
The library also contains extension method for dynamic LINQ
Wiki: Eval Dynamic LINQ
Example
// using Z.Expressions; // Don't forget to include this.
// The filterQuery must use the C# syntax
string filterQuery = "x.Id == 1 && x.Number = 2";
IQueryable<Publication> query = db.Publications.Include(i => i.Product);
query = query.Where(x => filterQuery);
EDIT: Answer sub question
Great but how can I filter by Product?
Entity Framework doesn't support filter in Include method.
However, EF+ do
Disclaimer: I'm the owner of Entity Framework Plus
See: EF+ Query IncludeFilter
By combining both libraries, you can achieve your desired result:
// Extension method must be registered, otherwise the library cannot be aware of which extension method exists!
EvalManager.DefaultContext.RegisterExtensionMethod(typeof (QueryIncludeFilterExtensions));
string where1 = "x.Id == 1 && x.Number == 2";
string where2 = "y.Id == 3";
var left2 = ctx.Publications
.Where(x => where1)
.Execute<IQueryable<Publication>>("IncludeFilter(x => x.Product.Where(y => " + where2 + "))")
.ToList();
If you want to try this solution, make sure you download the latest version of Eval-Expression.NET. We just have fixed few min ago an issue we found by trying your scenario.
EDIT2: Answer sub question
Here is what I recommend you.
Try the query without our library then try it dynamically after.
That's easier to find the compilation error or what you want to do really.
By example, we probably didn't understand the initial problem correctly and suggested IncludeFilter which you don't want. I assumed you wanted to filter multiple products. However, it looks you only have one product per publication, so the Where clause method obviously doesn't exist.
Maybe this is more what you are looking for:
string where1 = "x.Id == 1 && x.Number == 2 && x.Product.Id == 3";
var left2 = ctx.Publications
.Include(x => x.Product)
.Where(where1)
.ToList();
So in short, try the query by hardcoding it (without dynamic) then you will know how to use it dynamically after.
I’m having issues creating an IN clause using C# and lambdas.
I have the following method GetUserList(string filtersByRoles)
The variable string filtersByRoles can hold a comma-delimited value such as: “1,2” or “1,2,3” or “1,3,4” etc...each number represents the unique number of a Role (in other words, RoleId).
I then have the following C# lambda query:
var query = _userRepository.GetUserList();
Which returns an IQueryable<User> where User is a table from my EntityFramework.
Once I verify if the filtersByRoles parameter is not null-or-empty, I need to make an IN clause such as:
if (!string.IsNullOrEmpty(filtersByRoles))
{
//Convert *filtersByRoles* to an array of integers
int[] myArray = filtersByRoles.Split(',').Select(x => int.Parse(x)).ToArray();
//Make the IN clause
query = query.Where(u => myArray.Contains(u.RoleId));
}
The above code compiles...but at RUNTIME it fails with the following error message:
LINQ to Entities does not recognize the method 'Boolean
Contains[Int32](System.Collections.Generic.IEnumerable`1[System.Int32],
Int32)' method, and this method cannot be translated into a store
expression.
I’ve manage to find a workaround but it involves making a call to the .ToList() method which I believe fetches all the data from my database and then, adds a Where() clause.
But wouldn’t that defeat the purpose or create some performance issues?
This is what I’ve done:
if (!string.IsNullOrEmpty(filtersByRoles))
{
string[] myArray = filtersByRoles.Split(',');
query = query.ToList().Where(u => myArray.Contains(u.RoleId.ToString())).AsQueryable();
}
I would prefer not to make the .ToList() call and avoid fetching all the data.
Is there another way to achieve this?
EDIT:
I'm using Entity Framework 1.0 and .NET Framework 3.5
Thanks
Sincerely
Vince
Here are my 2 cents:
Maybe the Dynamic LinQ will help solve your problem:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
You could build your Where clause as a string, say something like :
string sWhereClause = "1 = 1";
foreach(string rId in filtersByRoles.Split(','))
sWhereClause += " OR RoleId = " + rId;
(I would suggest to use StringBuilder instead of +concat, but for the purpose of this answer, it doesn't matter)
and then
query = query.Where(sWhereClause);
I haven't tryed it though, but it sounds fair for solving your problem. Even though it looks like SQL injection... Well, improvements can be brought.
EDIT:
As a second thought I manage to come with this new idea:
string filterByRoles = "1,2,3";
query = query.Where(new Func<User, bool>(u => {
return filterByRoles.Contains(u.RoleId.ToString());
})).AsQueryable();
This way, you can add whatever code you want in the Func{ ... } delegate, as long as it returns a boolean (I assumed here your TInput was a "User" class, of course change it to use the one corresponding to you needs).
Hope this helps!
This is supported with EF 4 : http://blogs.msdn.com/b/alexj/archive/2009/03/26/tip-8-writing-where-in-style-queries-using-linq-to-entities.aspx
Based on some of your replies, I’ve manage to pull up something like this:
int[] myArray = filtersByRoles.Split(',').Select(x => int.Parse(x)).ToArray();
int count = myArray.Count();
int role1;
int role2;
int role3;
int role4;
switch (myArray.Length)
{
case 1:
role1 = myArray[0];
query = query.Where(u => u.RoleId.Equals(role1));
break;
case 2:
role1 = myArray[0];
role2 = myArray[1];
query = query.Where(u => u.RoleId.Equals(role1)
|| u.RoleId.Equals(role2));
break;
case 3:
role1 = myArray[0];
role2 = myArray[1];
role3 = myArray[2];
query = query.Where(u => u.RoleId.Equals(role1)
|| u.RoleId.Equals(role2)
|| u.RoleId.Equals(role3));
break;
case 4:
role1 = myArray[0];
role2 = myArray[1];
role3 = myArray[2];
role4 = myArray[3];
query = query.Where(u => u.RoleId.Equals(role1)
|| u.RoleId.Equals(role2)
|| u.RoleId.Equals(role3)
|| u.RoleId.Equals(role4));
break;
}
When directly trying with the myArray[xxx]:
query = query.Where(u => u.RoleId.Equals(myArray[0]));
I was getting this:
The LINQ expression node type 'ArrayIndex' is not supported in LINQ to
Entities.
Hence the creation of the 4 (integer) variables!
It now works but may need some optimization…
Thanks
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! :-)
I have an optional part of query that needs to be executed on a certain condition. Here is the example code:
int cat = 1;
int UserID = 12;
string qry = "select * from articles";
if(cat > 0)
qry += " where categoryID = " + cat;
if(UserID > 0)
qry += " AND userid = " + UserID; //The AND may be a WHERE if first condition is false
As you can see I have an if statement in the query. i am currently using Entity Framework and it does not support this kind of scenario. Is there an ORM out there that support this?
Edit
I tried to dummy down the query. But I have about 20 "IF" statements and the querys are very long.
The ORMs I was looking at were:
NHibernate
LLBLGen
Subsonic
I am open to any ORM. Thanks
As it was already mentioned here, LINQ allows to extend any query by simply adding more criteria to it.
var query =
from x in xs
where x==1
select x;
if (mustAddCriteria1)
query =
from x in query
where ... // criteria 1
select x;
if (mustAddCriteria2)
query =
from x in query
where ... // criteria 2
select x;
And so on. This approach works just perfectly. But likely, you know that compilation of LINQ queries is pretty expensive: e.g. Entity Framework can compile just about 500 relatively simple queries per second (see e.g. ORMBattle.NET).
On the other hand, many ORM tools support compiled queries:
You pass an IQueryable instance to some Compile method, and get a delegate allowing to execute it much faster later, because no recompilation would occur in this case.
But if we'd try to use this approach here, we immediately notice that our query is actually dynamic: IQueryable we execute each time might differ from the previous one. Presence of query parts there is determined by values of external parameters.
So can we execute such queries as compiled without e.g. explicit caching?
DataObjects.Net 4 support so-called "boolean branching" feature. It implies any constant boolean expression is evaluated during query compilation and its actual value is injected into SQL query as true boolean constant (i.e. not as parameter value or as an expression utilizing parameters).
This feature allows to generate different query plans dependently on values of such boolean expressions with ease. E.g. this code:
int all = new Random().Next(2);
var query =
from c in Query<Customer>.All
where all!=0 || c.Id=="ALFKI"
select c;
will be executed using two different SQL queries, and thus - two different query plans:
Query plan based on index seek (quite fast), if all==0
Query plan based on index scan (quite slow), if all!=0
Case when all==null, SQL query:
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE(( CAST( 0 AS bit ) <> 0 ) OR( [a].[CustomerId] = 'ALFKI' ) );
Case when all==null, query plan:
|--Compute Scalar(DEFINE:([Expr1002]=(111)))
|--Clustered Index Seek(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]), SEEK:([a].[CustomerId]=N'ALFKI') ORDERED FORWARD)
Second case (when all!=null), SQL query:
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE(( CAST( 1 AS bit ) <> 0 ) OR( [a].[CustomerId] = 'ALFKI' ) );
-- Notice the ^ value is changed!
Second case (when all!=null), query plan:
|--Compute Scalar(DEFINE:([Expr1002]=(111)))
|--Clustered Index Scan(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]))
-- There is index scan instead of index seek!
Note that almost any other ORM would compile this to a query utilizing integer parameter:
SELECT
[a].[CustomerId],
111 AS [TypeId] ,
[a].[CompanyName]
FROM
[dbo].[Customers] [a]
WHERE(( #p <> 0 ) OR ( [a].[CustomerId] = 'ALFKI' ) );
-- ^^ parameter is used here
Since SQL Server (as well as most of databases) generates a single version of query plan for a particular query, it has the only option in this case - generate a plan with index scan:
|--Compute Scalar(DEFINE:([Expr1002]=(111)))
|--Clustered Index Scan(OBJECT:([DO40-Tests].[dbo].[Customers].[PK_Customer] AS [a]), WHERE:(CONVERT(bit,[#p],0)<>(0) OR [DO40-Tests].[dbo].[Customers].[CustomerId] as [a].[CustomerId]=N'ALFKI'))
Ok, that was a "quick" explanation of usefulness of this feature. Let's return back to your case now.
Boolean branching allows to implement it in very simple fashion:
var categoryId = 1;
var userId = 1;
var query =
from product in Query<Product>.All
let skipCategoryCriteria = !(categoryId > 0)
let skipUserCriteria = !(userId > 0)
where skipCategoryCriteria ? true : product.Category.Id==categoryId
where skipUserCriteria ? true :
(
from order in Query<Order>.All
from detail in order.OrderDetails
where detail.Product==product
select true
).Any()
select product;
The example differs from yours, but it illustrates the idea. I used different model mainly to be able to test this (my example is based om Northwind model).
This query is:
Not a dynamic query, so you can safely pass it to Query.Execute(...) method to get it executed as compiled query.
Nevertheless each its execution will lead to the same result as if this would be done with "appending" to IQueryable.
this can be done using linq to sql...
IQueryable<Article> query = yourDataContext.Articles;
if (catId > 0)
query = query.Where(x => x.CategoryId == catId);
return query.ToList();
NHibernate supports this using the Criteria API:
ICriteria criteria = session.CreateCriteria<Article>();
if (cat > 0)
criteria.Add(Expression.Eq("categoryID", cat));
You can probably do this with any LINQ provider, but I know the LightSpeed ORM supports it:
var query = UnitOfWork.Articles;
if (cat > 0)
query = query.Where(a => a.CategoryId == cat);
I do this kind of thing in NHibernate all the time.
(I've done similar things in Rails. I'm kind of surprised that there are ORMs that don't support this.)
You can easily build queries in this way using NHibernate's HQL (Hibernate Query Language). It would be an almost identical implementation but I would personally use parameters.
public List<Article> GetCat(int cat)
{
string qry = "select ap from Article a";
if(cat > 0)
qry += " where a.categoryID = :cat";
IQuery query = session.CreateQuery(qry).SetInt32("cat",cat);
return query.List<Article>();
}
This returns a List<> of Article objects ready for use.
No love for LLBLGen? Well it can can do it too.
Using the 'adapter' style:
RelationPredicateBucket filters = new RelationPredicateBucket();
if (cat > 0)
filters.Predicate.Add(Article.Fields.CategoryID == cat);
if (userId > 0)
filters.Predicate.Add(Article.Fields.UserID == userId);
// And so on.
var adapter = new DataAccessAdapter();
var results = new EntityCollection<Article>(new ArticleFactory());
adapter.FetchEntityCollection(results, filters);
I would suspect most ORMs should be able to do this pretty easily.
You can use the Predicate Builder and LINQ to NHibernate to generate dynamic query's like this:
//using Predicate Builder
public List<Location> FindAllMatching(string[] filters)
{
var db = Session.Linq<Location>();
var expr = PredicateBuilder.False<Location>(); //-OR-
foreach (var filter in filters)
{
string temp = filter;
expr = expr.Or(p => p.Name.Contains(temp));
}
return db.Where(expr).ToList();
}
You get the advantage of Type Save Query's and Compiler check.
You can also use the same approach of predicate builder with Linq to Sql and Entity Framework.
EDIT: Added example.
It could be something like get all the locations matching N regions of the world, where the user select the regions he want to see, we don't know how many the user will select, we must build the (OR) expression on the fly, you can do something like:
public ActionResult Action(string[] filters)
{
/*This values are provided by the user, maybe its better to use
an ID instead of the name, but for the example is OK.
filters will be something like : string[] filters = {"America", "Europe", "Africa"};
*/
List<Location> LocationList = FindAllMatchingRegions(filters);
return View(LocationList);
}
public List<Location> FindAllMatchingRegions(string[] filters)
{
var db = Session.Linq<Location>();
var expr = PredicateBuilder.False<Location>(); //-OR-
foreach (var filter in filters)
{
string temp = filter;
expr = expr.Or(p => p.Region.Name == filter);
}
return db.Where(expr).ToList();
}
You can Nest Predicates for a complex scenarios like this:
If you want to do something like
p => p.Price > 99 &&
p.Price < 999 &&
(p.Description.Contains ("foo") || p.Description.Contains ("far"))
you can build:
var inner = PredicateBuilder.False<Product>();
inner = inner.Or (p => p.Description.Contains ("foo"));
inner = inner.Or (p => p.Description.Contains ("far"));
var outer = PredicateBuilder.True<Product>();
outer = outer.And (p => p.Price > 99);
outer = outer.And (p => p.Price < 999);
outer = outer.And (inner);
And use it like :
var pr = db.Products.Where(outer).ToList();
The Predicate Builder Source and examples are available at http://www.albahari.com/nutshell/predicatebuilder.aspx
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. :)