tags:

views:

367

answers:

8

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

+2  A: 

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);
itowlson
I tried to dummy down the query. But I have about 20 "IF" statements and the querys are very long. Is it not possible to chain the IF statements in the linq query itself?
Luke101
If you chain the if statements in the LINQ query, then they'll get translated to SQL, and whether this will work will depend on the LINQ provider. But you can chain .Where invocations under the control of client-side if statements.
itowlson
Also, if your real use case is considerably more complicated, you might want to edit your question to give a bit more of a flavour of your real use case so that you don't get a load of answers like mine that only describe the trivial case!
itowlson
A: 

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.)

Ken
Would you have an example of how it is done in nhibernate?
Luke101
Not easily -- we've got our own abstraction over NHibernate that I use 99.9% of the time. See Kevin's answer above.
Ken
Kevin's answer nailed it.
Chris Conway
+7  A: 

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();
tt83
I have editied the code. is it possible to add multiple where statements in a linq query? See the code in the question
Luke101
Yes. IQueryable<Article> in this case will defer the SQL execution until you materialize it (call ToList, etc.) you can add as many conditionals as you like. Only when you call ToList will it actually execute the SQL against the database.
Michael Gattuso
Would I add another conditional like this: query = query.Where(x => x.CategoryId == catId); query += query.Where(x => x.userid == UserID);
Luke101
Second query: query += query.Where... should be just: query = query.Where...
Michael Gattuso
We use this too, and it works great. We have a lot of Where statements too. Even building up expressions to use with Or and And too. Works great :)@Luke101: you don't use `+=`. Just `=`.
Svish
Note, that such can be used not only in Lniq-To-Sql, but in generally any ORM supporting LINQ.
Alex Kofman
Omar
See my answer - it allows to compose everything "as is".
Alex Yakunin
A: 

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.

Jay
+6  A: 

NHibernate supports this using the Criteria API:

ICriteria criteria = session.CreateCriteria<Article>();

if (cat > 0)
    criteria.Add(Expression.Eq("categoryID", cat));
Kevin Pang
I totally agree with this. The ICriteria was designed specifically for this type of scenario, dynamic queries defined at runtime.
Chris Conway
A: 

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

Omar
I am curious of this approach. Can you give an example of the kind of data the variable "Filters" would contain. Also, how would the caller use the return value?
Luke101
I updated my post with an example, its pretty simple but you can do pretty much anything with the predicate builder, like Nesting Predicates inner/outer expression.
Omar
A: 

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.

+10  A: 

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.
Alex Yakunin
I forgot to add why this is convenient: such query can be used as compiled query in DO4. Obviously, DO4 will care about using appropriate SQL query. If you don't have this feature, but it's necessary to pre-compile such a query, you should achieve the same only with a set of "if"s and a set of compiled queries. 2 conditions = 4 compiled queries. 3 conditions = 8 compiled queries, and so on.
Alex Yakunin
@Alex, this is nice - but can you point to where this is shown in your product's documentation? The point I'm making is, if a feature isn't discoverable - it doesn't exist. Which is a shame for a great product like DataObjects.net.
Alex
True. Actually the documentation itself (manual) is being written right now, and this feature isn't described there yet (we still have a set of much more important ones to describe...). It must appear quite soon (days ... week). Its latest revision is always available here: http://dataobjectsdotnet.googlecode.com/hg/Manual/index.htm
Alex Yakunin