C#,Linq2Sql:是否可以将两个可查询连接成一个?

时间:2009-02-20 14:07:52

标签: c# linq-to-sql lambda expression-trees iqueryable

我有一个可查询的,其中我使用了各种WhereWhereBetween语句将集合缩小到某个集合。现在我需要添加一种Where || WhereBetween 。换句话说,我不能像现在一样把它们连在一起,因为它可以作为一个And。那么,我该怎么做呢?

我看到两种可能性:

  1. 创建两个可查询,一个使用Where,另一个使用WhereBetween然后将它们连接起来。不知道这是否可能?此外,虽然不是在我的特定情况下,你很可能最终会重复...
  2. 以某种方式Where表达式与WhereBetween 中创建的表达式合并为某种Or。

  3. 首先,如上所述,我不确定是否可能。如果是的话,我不太确定这是一个很好的方法。

    第二,我可以看到一个选项,但不完全确定所有细节。以下是我的另一个问题中的WhereBetween方法,我现在使用它并且效果很好:

        public static IQueryable<TSource> WhereBetween<TSource, TValue>(
            this IQueryable<TSource> source,
            Expression<Func<TSource, TValue>> selector,
            IEnumerable<Range<TValue>> ranges)
        {
            var param = Expression.Parameter(typeof(TSource), "x");
            var member = Expression.Invoke(selector, param);
            Expression body = null;
            foreach (var range in ranges)
            {
                var filter = Expression.AndAlso(
                    Expression.GreaterThanOrEqual(member,
                         Expression.Constant(range.A, typeof(TValue))),
                    Expression.LessThanOrEqual(member,
                         Expression.Constant(range.B, typeof(TValue))));
                body = body == null ? filter : Expression.OrElse(body, filter);
            }
            return body == null ? source : source.Where(
                Expression.Lambda<Func<TSource, bool>>(body, param));
        }
    

    我想我可以将表达式构建部分提取到一个新方法中。也许是这样的:

        public static IQueryable<TSource> WhereBetween<TSource, TValue>(
            this IQueryable<TSource> source,
            Expression<Func<TSource, TValue>> selector,
            IEnumerable<Range<TValue>> ranges)
        {
            return source.Where(WhereBetween(selector, ranges));
        }
    
        public static Expression<Func<TSource, bool>> WhereBetween<TSource, TValue>(
            Expression<Func<TSource, TValue>> selector,
            IEnumerable<Range<TValue>> ranges)
        {
            var param = Expression.Parameter(typeof(TSource), "x");
            var member = Expression.Invoke(selector, param);
            Expression body = null;
            foreach (var range in ranges)
            {
                var filter = Expression.AndAlso(
                    Expression.GreaterThanOrEqual(member,
                         Expression.Constant(range.A, typeof(TValue))),
                    Expression.LessThanOrEqual(member,
                         Expression.Constant(range.B, typeof(TValue))));
                body = body == null ? filter : Expression.OrElse(body, filter);
            }
            return body == null 
                ? ø => true
                : Expression.Lambda<Func<TSource, bool>>(body, param);
        }
    

    然后,我可以使用该新方法来获取表达式而不是可查询表达式。所以,假设我有WhereBetween(ø => ø.Id, someRange),例如ø => ø.SomeValue == null。我如何将这两者与Or结合起来?我正在查看Expression.OrElse方法中使用的WhereBetween,我认为这可能是我需要的,或者可能是Expression.Or。但我对这些表达方式非常不稳定,所以我不确定在这里选择什么,或者即使我走在正确的轨道上:p

    有人可以给我一些指示吗?

1 个答案:

答案 0 :(得分:9)

这里有两个选项 - Queryable.Union或表达式组合。我通常赞成后者,通过OrElse - 你可以使用2个表达式(见下文) - (至少使用LINQ-to-SQL) - 但在任何一种情况下它都应该被组合:

    using(var ctx = new DataClasses1DataContext())
    {
        ctx.Log = Console.Out;
        Expression<Func<Customer, bool>> lhs =
            x => x.Country == "UK";
        Expression<Func<Customer, bool>> rhs =
            x => x.ContactName.StartsWith("A");

        var arr1 = ctx.Customers.Where(
            lhs.OrElse(rhs)).ToArray();

        var arr2 = ctx.Customers.Where(lhs)
            .Union(ctx.Customers.Where(rhs)).ToArray();
    }

arr1arr2每个只执行1次数据库命中(尽管TSQL不同;第一个在OR子句中有WHERE;第二个有两个使用UNION)单独查询。

这是我使用的扩展方法:

static Expression<Func<T, bool>> OrElse<T>(
    this Expression<Func<T, bool>> lhs,
    Expression<Func<T, bool>> rhs)
{
    var row = Expression.Parameter(typeof(T), "row");
    var body = Expression.OrElse(
        Expression.Invoke(lhs, row),
        Expression.Invoke(rhs, row));
    return Expression.Lambda<Func<T, bool>>(body, row);
}