Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IQueryable with Foreach on the result

I'm trying to do a keyword search on an IQueryable object, but is there any way to do this without first converting it into a list?

My code:

var customers = Customer.IQueryableAll(); // Method returns IQueryable containing all customers.    
var customerHolder = new List<Customer>();

foreach(var k in keywords) //keywords = List<string>
{
   customerHolder.AddRange(customers.Where(x=>x.FirstName.Contains(k) || x.LastName.Contains(k) || x.CompanyName.Contains(k)).ToList())
}

return customerHolder.Distinct();

This works if I want to display all the results at once, but the problem comes where I want to do paging. The function would still get all the entries from the database, before paging, so its very inefficient on large tables. (ie. customerHolder.Skip(5).Take(5); )

Is there any way to integrate the foreach searching part into the query itself ?

ie.

customers.Where( x => x.Name.Contains(anythingInKeyWords));

EDIT: For further clarification, I do want to maintain the OR in the above, so filtering and refiltering with multiple where clauses will not work. IE. Bill Job/Bill Gates > Search Bill Gates should return both entries because Bill Matches.

like image 829
mildse7en Avatar asked Jan 22 '14 17:01

mildse7en


People also ask

Can you foreach an IQueryable?

If you use IQueryable with foreach then query will be evaluated and executed at the same time. Some of the important points about IQueryable<T> are as follows: It implements IEnumerable <T> so the results can be iterated using foreach. It is best suited to query against external data sources.

How do I combine two IQueryable results?

var list4 = list1. Union(list2); Union is a set operation - it returns distinct values. Concat simply returns the items from the first sequence followed by the items from the second sequence; the resulting sequence can include duplicate items.

What is the use of IQueryable in c#?

The IQueryable interface inherits the IEnumerable interface so that if it represents a query, the results of that query can be enumerated. Enumeration causes the expression tree associated with an IQueryable object to be executed. The definition of "executing an expression tree" is specific to a query provider.

How do I add data to IQueryable?

The simple answer is that unless you add the record to the underlying datastore that the Iqueryable is querying, you can't add a new record into an IQueryable. So if you are using LinqToSql then you would have to add a row into the table that the IQueryable was querying in order to "add" a row into the IQueryable.


1 Answers

You need to build a query that ORs the result of your filter expression for each keyword per entity, which isn't very practical without using dynamic LINQ. Here's an extension method that will do just that for you:

public static class ExtensionMethods
{
    public static IQueryable<TEntity> TestPerKey<TEntity, TKey>( 
        this IQueryable<TEntity> query, 
        IEnumerable<TKey> keys, 
        Expression<Func<TEntity, TKey, bool>> testExpression )
    {
        // create expression parameter
        var arg = Expression.Parameter( typeof( TEntity ), "entity" );

        // expression body var
        Expression expBody = null;

        // for each key, invoke testExpression, logically OR results
        foreach( var key in keys )
        {
            // constant expression for key
            var keyExp = Expression.Constant( key );

            // testExpression.Invoke expression
            var invokeExp = Expression.Invoke( testExpression, arg, keyExp );

            if( null == expBody )
            {
                // first expression
                expBody = invokeExp;
            }
            else
            {
                // logically OR previous expression with new expression
                expBody = Expression.OrElse( expBody, invokeExp );
            }
        }

        // execute Where method w/ created filter expression
        return query.Where( ( Expression<Func<TEntity, bool>> )Expression.Lambda( expBody, arg ) );
    }
}

Usage:

class TestEntity
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string CompanyName { get; set; }
}

static void Main()
{
    var testCollection = new TestEntity[]{
        new TestEntity(){
            Id = 0,
            FirstName = "abc",
            LastName = "def",
            CompanyName = "ghi"
        },
        new TestEntity(){
            Id = 1,
            FirstName = "def",
            LastName = "ghi",
            CompanyName = "jkl"
        },
        new TestEntity(){
            Id = 2,
            FirstName = "ghi",
            LastName = "jkl",
            CompanyName = "mno"
        },
        new TestEntity(){
            Id = 3,
            FirstName = "bcd",
            LastName = "efg",
            CompanyName = "hij"
        },
    };

    var keywords = new[]{
            "abc",
            "jkl"
        };

    var query = testCollection.AsQueryable()
        .TestPerKey( 
            keywords,
            ( t, k ) => 
                t.FirstName.Contains( k ) || 
                t.LastName.Contains( k ) || 
                t.CompanyName.Contains( k ) );

    foreach( var result in query )
    {
        Console.WriteLine( result.Id );
    }
}

Update - try the following extension method. It is more specific but should work with EF:

    public static IQueryable<TestEntity> TestPerKey(
        this IQueryable<TestEntity> query,
        IEnumerable<string> keys )
    {
        MethodInfo containsMethodInfo = typeof( string ).GetMethod( "Contains" );

        // create expression parameter
        var arg = Expression.Parameter( typeof( TestEntity ), "entity" );

        // expression body var
        Expression expBody = null;

        // for each key, invoke testExpression, logically OR results
        foreach( var key in keys )
        {
            var expression = Expression.OrElse(
                Expression.OrElse(
                    Expression.Call( Expression.Property( arg, "FirstName" ), containsMethodInfo, Expression.Constant( key ) ),
                    Expression.Call( Expression.Property( arg, "LastName" ), containsMethodInfo, Expression.Constant( key ) ) )
                , Expression.Call( Expression.Property( arg, "CompanyName" ), containsMethodInfo, Expression.Constant( key ) ) );

            if( null == expBody )
            {
                // first expression
                expBody = expression;
            }
            else
            {
                // logically OR previous expression with new expression
                expBody = Expression.OrElse( expBody, expression );
            }
        }

        // execute Where method w/ created filter expression
        return query.Where( ( Expression<Func<TestEntity, bool>> )Expression.Lambda( expBody, arg ) );
    }
like image 152
Moho Avatar answered Nov 15 '22 09:11

Moho