【问题标题】:IQueryable with Foreach on the resultIQueryable 与 Foreach 对结果
【发布时间】:2014-02-12 22:46:43
【问题描述】:

我正在尝试对 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 > Search Bill Gates 应该返回两个条目,因为比尔匹配。

【问题讨论】:

  • 唯一的方法是动态构建过滤表达式(到 OR 条件)。

标签: c# asp.net sql linq iqueryable


【解决方案1】:

您需要构建完整的 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】:

    您需要构建一个查询,对每个实体的每个关键字的过滤器表达式的结果进行 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 ) );
        }
    }
    

    用法:

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

    【讨论】:

    • 很好,但遗憾的是它不能立即与实体框架一起工作(“LINQ to Entities 不支持 LINQ 表达式节点类型'Invoke'。”)。但是,您可以通过在来自 EF 的 IQueryable 上调用 Linqkit 的 AsExpandable() 来使其工作。
    • 啊,真的。您必须使方法更具体并动态生成整个谓词。
    • 添加了另一个扩展方法,该方法应该与 EF 一起使用,但其谓词是硬编码的
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 2011-06-21
      • 2015-07-30
      • 2014-12-05
      • 2020-01-27
      • 1970-01-01
      • 2019-11-11
      • 2013-09-03
      • 2013-08-24
      • 2011-04-29
      相关资源
      最近更新 更多