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

mil*_*7en 6 c# sql linq asp.net 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();
Run Code Online (Sandbox Code Playgroud)

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

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

即.

customers.Where( x => x.Name.Contains(anythingInKeyWords));
Run Code Online (Sandbox Code Playgroud)

编辑:为了进一步澄清,我确实想要保持上面的OR,所以使用多个where子句进行过滤和重新过滤将不起作用.IE浏览器.Bill Job/Bill Gates>搜索Bill Gates应该返回两个条目,因为比尔匹配.

Moh*_*oho 6

您需要为每个实体的每个关键字构建一个OR表示过滤器表达式结果的查询,这在不使用动态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 ) );
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

更新 - 尝试以下扩展方法.它更具体,但应与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 ) );
    }
Run Code Online (Sandbox Code Playgroud)