在结果上使用Foreach可以实现智能化

时间:2014-01-22 17:54:37

标签: c# asp.net sql linq iqueryable

我正在尝试对IQueryable对象进行关键字搜索,但有没有办法在没有先将其转换为列表的情况下执行此操作?

我的代码:

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();

如果我想一次显示所有结果,这是有效的,但问题出现在我想要进行分页的地方。在分页之前,该函数仍然会从数据库中获取所有条目,因此它对大型表的效率非常低。 (即customerHolder.Skip(5).Take(5);

有没有办法将foreach搜索部分集成到查询本身?

即。

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

编辑:为了进一步澄清,我确实想要维护上面的OR ,因此使用多个where子句进行过滤和重新过滤将不起作用。 IE浏览器。 Bill Job / Bill Gates&gt;搜索比尔盖茨应该返回两个条目,因为比尔匹配。

3 个答案:

答案 0 :(得分:5)

您需要构建一个查询,为每个实体的每个关键字计算过滤表达式的结果,如果不使用动态LINQ,这个查询就不太实用了。这是一个扩展方法,可以为您做到这一点:

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 ) );
    }
}

用法:

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 );
    }
}

更新 - 尝试以下扩展方法。它更具体,但应与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 ) );
    }

答案 1 :(得分:0)

您需要构建完整的IQueryable查询,并在最后执行它。 试试这样:

keywords.All(k=>{customers = customers.Where(x=>x.FirstName.Contains(k) || x.LastName.Contains(k) || x.CompanyName.Contains(k)); return true;});

var result = customers.ToList();

答案 2 :(得分:-5)

不,您必须使用ToList来获得所需的结果,因为LINQ完全是集合基础对象术语。