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.
Related
I am trying to mimic below statement in Linq to SQL.
WHERE (rtrim(posid) like '%101' or rtrim(posid) like '%532')
I statement basically determine if posid ends with 101 or 532. In the above example I am only making 2 comparisons but their could be 1 to N comparisons all joined with OR. I store the comparison values (101,532,...) in a generic list that I send to my Linq to SQL method.
I have tried to mimic above SQL using a where clause unsuccessfully (example below):
var PosNum = new List<string>();
PosNum.Add("101");
PosNum.Add("532");
var q = (from a in context.tbl_sspos select a);
q = q.Where(p => PosNum.Contains(p.posid.Trim()));
The issue with the above where clause is that it tries to do an exact match rather I want an ends with comparison.
How would I mimic the SQL statement in Linq to SQL.
Thank You in advance for any help / advice you can provide.
I would use String.EndsWith();
This will check the end of the string rather than entire contents of it.
var q = (from a in context.tbl_sspos select a);
q = q.Where(p => p.posid.EndsWith("102") || p.posid.EndsWith("532"));
In EF 4 you can use the StartsWith / EndsWith methods by now. Might also work in LINQ to SQL.
UPDATE
Just realized that you are trying todo this against multiple values (PosNum), I don't think that this is directly supported currently. You can however concatenate multiple Where()clauses to get the result.
UPDATE 2
As AdamKing pointed out concatenating the where clauses was filtering against all PosNum values, here is the corrected version:
var baseQuery = (from a in context.tbl_sspos select a);
IEnumerable<YourType> q = null;
foreach(var pos in PosNum)
{
if(q == null)
q = baseQuery.Where(a => a.posid.EndsWith(pos));
else
q = q.Union(baseQuery.Where(a => a.posid.EndsWith(pos)));
}
This is not as pretty anymore, but works nonetheless.
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 would like to append a set of conditional where clauses onto the end of an ObjectSet. However, the clauses do not get executed and instead the original query is run, for example:
using (Entities context = new Entities()){
var q = context.AuditLogs;
q.Where(o => o.WebsiteId == 1);
}
The where clause is not executed and the full result set is returned
I could instead use IQueryAble as in:
var q = context.AuditLogs.AsQueryable();
q = q.Where(o => o.WebsiteId == 1);
However this loses me the power of being able to use .Include to eager load my related entities.
No, it won't. at any point before executing the query, you would still be able to cast it back to ObjectQuery<T> and invoke methods like Include on it:
var query = context.AuditLogs.AsQueryable();
query = query.Where(o => o.WebsiteId == 1);
var auditLog = ((ObjectQuery<AuditLog>)query).Include("yourNavPropertyName")
.ToList();
If your intention is to build up a criteria incrementally, then the other option would be to leverage EntitySQL with QueryBuilder methods:
var query = context.AuditLogs.Where("it.WebsiteId = 1");
query = query.Where("...");
var auditLog = query.Include("yourNavPropertyName")
.ToList();
Just some good old fashioned linq would suffice here. Assuming you had a property named SiteOwner you could accomplish what your trying to do with the below query
using (Entities context = new Entities()){
var webSites = from sites in context.AuditLogs.Include("SiteOwner")
where sites.WebSiteId == 1
select sites;
}
Im working on an source code with an sql query in a VAR type like
var query = select ... from ... where ... ;
is it possible to add an dynamic "where clause" like
string condition = "where x.x > x.y";
e.g. var query = select ... from ... + condition;
Iam sorry for my bad english
You are not clearly stating how your query looks like. Is it a result of a LINQ operation or simply a String?
The keyword var is only usable for design time. The compiler will substitute it with the correct datatype.
If you SQL query is a string, like
var query = "Select ... from ... where ..";
then
string condition = "where x.x > x.y";
query += condition;
is valid because both variables are strings. You can't combine a non string type with a string the way your code suggests.
I do now assume that you are using a LINQ syntax. It is possible to add such conditions to a linq query per code, I think the keywords linq query builder, expression tree and predicate should get you started.
I'd strongly suggest that you stop using the var keyword without exactly knowing what it does and where to use it.
Dynamic Linq exists specifically to solve late-bound scenarios for LINQ:
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
Allows constructs such as:
NorthwindDataContext northwind = new NorthwindDataContext();
var query = northwind.Products
.Where("CategoryID = 3 AND UnitPrice > 3")
.OrderBy("SupplierID");
If you do not call ToList() and your final mapping to the DTO type, you can add Where clauses as you go, and build the results at the end:
var query = from u in DataContext.Users
where u.Division == strUserDiv
&& u.Age > 18
&& u.Height > strHeightinFeet
select u;
if (useAge)
query = query.Where(u => u.Age > age);
if (useHeight)
query = query.Where(u => u.Height > strHeightinFeet);
// Build the results at the end
var results = query.Select(u => new DTO_UserMaster
{
Prop1 = u.Name,
}).ToList();
This will still only result in a single call to the database, which will be effectively just as efficient as writing the query in one pass.
I saw this answer here by Reed Copsey
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