views:

150

answers:

3

Is there a way to make the ProjectID check below part of an optional block? I'm a recent .Net convert from JEE and I'm looking for something similar to the Hibernate Criteria API. I'd like to simplify the block below and only have to call Where() once. I'm also not sure of the performance implications of doing a Where() with lambdas as I just started working with .Net a week ago.

public IQueryable<Project> FindByIdOrDescription(string search)
    {
        int projectID;
        bool isID = int.TryParse(search, out projectID);

        IQueryable<Project> projects;

        if (isID)
        {
            projects = dataContext.Projects.Where(p => p.ProjectDescription.Contains(search) || p.ProjectID == projectID);
        }
        else
        {
            projects = dataContext.Projects.Where(p => p.ProjectDescription.Contains(search));
        }

        return projects;
    }
A: 

Delegates / expressions can be "chained", like this (untested pseudo-code):

Expression<Predicate<Project>> predicate = p => p.ProjectDescription.Contains(search);
if ( isID ) 
    predicate = p => predicate(p) || p.ProjectID == projectId;

return dataContext.Where(predicate);
driis
Unfortunately that won't even compile. Expressions cannot be called inside expressions like this.
Ruben
Is there another way to do this in LINQ? Perhaps without Lambdas?
jcm
I've provided an example that should work, but it ain't pretty.
Ruben
+1  A: 

The query isn't parsed and executed until the first time you actually access the elements of the IQueryable. Therefore, no matter how many where's you keep appending (which you're not even doing here anyway) you don't need to worry about hitting the DB too many times.

BFree
+2  A: 

If you're looking for optionally adding AND xyz, you could simply chain Where calls:

var projects = dataContext.Projects.Where(p => p.ProjectDescription.Contains(search));
if (isID)
    projects = projects.Where(p => p.ProjectID == projectID);

Unfortunately things are not so easy when you'd like to do an OR xyz. For this to work, you'll need to build a predicate expression by hand. It's not pretty. One way to do this is

Expression<Func<Project, bool>> predicate = p => p.ProjectDescription.Contains(search);
if (isID)
{
    ParameterExpression param = expr.Body.Parameters[0];
    predicate = Expression.Lambda<Func<Project, bool>>(
        Expression.Or(
            expr.Body,
            Expression.Equal(
                Expression.Property(param, "ProjectID"),
                Expression.Constant(projectID))),
        param);
}
var projects = dataContext.Projects.Where(predicate);

Note that adding a condition to the existing predicate is a lot more work than creating the initial expression, because we need to completely rebuild the expression. (A predicate must always use a single parameter; declaring two expressions using lambda syntax will create two separate parameter expression objects, one for each predicate.) Note that the C# compiler does roughly the same behind the scenes when you use lambda syntax for the initial predicate.

Note that this might look familiar if you're used to the criteria API for Hibernate, just a little more verbose.


Note, however, that some LINQ implementations are pretty smart, so the following may also work:

var projects = dataContext.Projects.Where(
    p => p.ProjectDescription.Contains(search)
      || (isID && p.ProjectID == projectID));

YMMV, though, so do check the generated SQL.

Ruben
Yea this looks substantially more complicated than creating a new Criterion in Hibernate. I'm guessing LINQ doesn't support anything like that.
jcm
True, it's very verbose. However, there might be a shortcut for your situation. I've expanded the description to mention this.
Ruben