Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are Linq-To-Sql Dynamic-Where-Clauses Even Possible in Framework 3.5?

UPDATE: It Is Now Working
I was able to finally get it completed. A working-example is detailed in an answer below (which I will be able to mark-off in 2 days).


Everything Below Here Was Part of the Original Question

For the last 3 days, I have been trying to build a dynamic-where-clause on a DBML DataContext using code samples from questions posted here and from other sources as well...none have worked!

For the reasons below, I am beginning to wonder if this is even POSSIBLE using under Framework 3.5:

  1. Predicate Builder notes Framework 4.0 on their site.
  2. Some answers here talk about an equivolent Invoke versions in 4.0 (so I have some hope here).
  3. ...I could go on but you get the idea.

I am really at a loss and seem to be "grabbing at strings"...and I need some sound advice on how to approach this.

Original Version Had SOME Success But Only When:
The ONLY time I had a 'inkling' of success the data came-up (all 6178 rows of it) but no WHERE CLAUSE was applied. This was evidenced by the lack of any WHERE CLAUSE applied into the SQL found in the dataContext.GetCommand(query).CommandText.

Other Version #1 Fails:
And generates this error: "Method 'System.Object DynamicInvoke(System.Object[])' has no supported translation to SQL."

// VERSION 1:
public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> True<T>() { return f => true; }
    public static Expression<Func<T, bool>> False<T>() { return f => false; }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
    }
    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
    }
    public static Expression<Func<T, bool>> StringLike<T>(Expression<Func<T, string>> selector, string pattern)
    {
        var predicate = PredicateBuilder.True<T>();
        var parts = pattern.Split('%');
        if (parts.Length == 1) // not '%' sign
        {
            predicate = predicate.And(s => selector.Compile()(s) == pattern);
        }
        else
        {
            for (int i = 0; i < parts.Length; i++)
            {
                string p = parts[i];
                if (p.Length > 0)
                {
                    if (i == 0)
                    {
                        predicate = predicate.And(s => selector.Compile()(s).StartsWith(p));
                    }
                    else if (i == parts.Length - 1)
                    {
                        predicate = predicate.And(s => selector.Compile()(s).EndsWith(p));
                    }
                    else
                    {
                        predicate = predicate.And(s => selector.Compile()(s).Contains(p));
                    }
                }
            }
        }
        return predicate;
    }
}
// VERSION 1:
public List<QuickFindResult> QueryDocuments(string searchText, string customerSiteId, List<int> filterIds)
{
    var where = PredicateBuilder.True<vw_QuickFindResult>();

    var searches = new List<String>(searchText.Split(' '));
    searches.ForEach(productName =>
    {
        string like = productName.Replace('"', '%')
                                 .Replace('*', '%');

        where = PredicateBuilder.StringLike<vw_QuickFindResult>(x => x.DocumentName, like);
    });


    var results = DocumentCollectionService.ListQuickFind(where, null);

    // Do other stuff here...

    return results;
}
// VERSION 1:
public static List<vw_QuickFindResult> ListQuickFind(Expression<Func<vw_QuickFindResult, bool>> where, Expression<Func<vw_QuickFindResult, bool>> orderBy)
{
    var connectionString = GetConnectionString(ES_DOCUMENTS_CONNECTION_NAME);
    List<vw_QuickFindResult> results = null;

    using (HostingEnvironment.Impersonate())
    {
        using (var dataContext = new ES_DocumentsDataContext(connectionString))
        {
            IQueryable<vw_QuickFindResult> query = dataContext.vw_QuickFindResults;
            query = query.Where(where);

            results = query.ToList();
        }
    }

    return results;
}

Other Version #2 Fails:
And generates this error: "Method 'Boolean Like(System.String, System.String)' cannot be used on the client; it is only for translation to SQL."

// VERSION 2:
public List<QuickFindResult> QueryDocuments(string searchText, string customerSiteId, List<int> filterIds)
{
    Func<vw_QuickFindResult, bool> where = null;
    Func<string, Func<vw_QuickFindResult, bool>> buildKeywordPredicate = like => x => SqlMethods.Like(x.DocumentName, like);
    Func<Func<vw_QuickFindResult, bool>, Func<vw_QuickFindResult, bool>, Func<vw_QuickFindResult, bool>> buildOrPredicate = (pred1, pred2) => x => pred1(x) || pred2(x);

    // Build LIKE Clause for the WHERE
    var searches = new List<String>(searchText.Split(' '));
    searches.ForEach(productName =>
    {
        string like = productName.Replace('"', '%')
                                 .Replace('*', '%');

        where = (where == null) ? buildKeywordPredicate(like) : buildOrPredicate(where, buildKeywordPredicate(like));
    });

    var results = DocumentCollectionService.ListQuickFind(where, null);

    // Do other stuff here...

    return results;
}
// VERSION 2:
public static List<vw_QuickFindResult> ListQuickFind(Expression<Func<vw_QuickFindResult, bool>> where, Expression<Func<vw_QuickFindResult, bool>> orderBy)
{
    var connectionString = GetConnectionString(ES_DOCUMENTS_CONNECTION_NAME);
    List<vw_QuickFindResult> results = null;

    using (HostingEnvironment.Impersonate())
    {
        using (var dataContext = new ES_DocumentsDataContext(connectionString))
        {
            var query = dataContext.vw_QuickFindResults.AsEnumerable();
            query = query.Where(where);

            results = query.ToList();
        }
    }

    return results;
}
like image 507
Prisoner ZERO Avatar asked Nov 04 '22 11:11

Prisoner ZERO


1 Answers

Did you try building the query yourself using only Exression classes? There should be no particular problems there. It is actually relatively easy to learn. You can write a sample query, and then in debugging see how it is composed:

Expression<Func<string, bool>> exp = (s) => s.Contains("your query");

Then simply look at the exp variable in the watch, and you can see the structure. This particular example should be composed like this:

Expression constant = Expression.Constant("your query");
Expression p = Expression.Param(typeof(string);
Expression contains = Expression.Call(p, "Contains", constant);
Expression<Func<string, bool>> lambda = Expression.Lamba(contains, p);
//  Now you can send this to your ORM
like image 199
Vladimir Perevalov Avatar answered Nov 11 '22 13:11

Vladimir Perevalov