2

How can this method be changed to act as a NotInRange? It should return only the items where the predicate does not match of the the supplied values.

Update
The method name, InRange, is a bit misleading and should probably be WhereInRange (or similar), since it does not return a Boolean; NotInRange should then be WhereNotInRange.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace ConsoleApplication5 {
    /// SAMPLE USAGE
    class Program {
        static void Main(string[] args) {
            // get some ids to play with...
            string[] ids;
            using(var ctx = new DataClasses1DataContext()) {
                ids = ctx.Customers.Select(x => x.CustomerID)
                    .Take(100).ToArray();
            }

            // now do our fun select - using a deliberately small
            // batch size to prove it...
            using (var ctx = new DataClasses1DataContext()) {
                ctx.Log = Console.Out;
                foreach(var cust in ctx.Customers
                        .InRange(x => x.CustomerID, 5, ids)) {
                    Console.WriteLine(cust.CompanyName);
                }
            }
        }
    }

    /// THIS IS THE INTERESTING BIT
    public static class QueryableChunked {
        public static IEnumerable<T> InRange<T, TValue>(
                this IQueryable<T> source,
                Expression<Func<T, TValue>> selector,
                int blockSize,
                IEnumerable<TValue> values) {
            MethodInfo method = null;
            foreach(MethodInfo tmp in typeof(Enumerable).GetMethods(
                    BindingFlags.Public | BindingFlags.Static)) {
                if(tmp.Name == "Contains" && tmp.IsGenericMethodDefinition
                        && tmp.GetParameters().Length == 2) {
                    method = tmp.MakeGenericMethod(typeof (TValue));
                    break;
                }
            }
            if(method==null) throw new InvalidOperationException(
                "Unable to locate Contains");
            foreach(TValue[] block in values.GetBlocks(blockSize)) {
                var row = Expression.Parameter(typeof (T), "row");
                var member = Expression.Invoke(selector, row);
                var keys = Expression.Constant(block, typeof (TValue[]));
                var predicate = Expression.Call(method, keys, member);
                var lambda = Expression.Lambda<Func<T,bool>>(
                      predicate, row);
                foreach(T record in source.Where(lambda)) {
                    yield return record;
                }
            }
        }
        public static IEnumerable<T[]> GetBlocks<T>(
                this IEnumerable<T> source, int blockSize) {
            List<T> list = new List<T>(blockSize);
            foreach(T item in source) {
                list.Add(item);
                if(list.Count == blockSize) {
                    yield return list.ToArray();
                    list.Clear();
                }
            }
            if(list.Count > 0) {
                yield return list.ToArray();
            }
        }
    }
}

This method has been copied from Marc Gravell's answer to this question.

Community
  • 1
  • 1
David Murdoch
  • 87,823
  • 39
  • 148
  • 191

2 Answers2

1

I think you need to add this to the predicate

var predicate = Expression.Not(Expression.Call(method, keys, member));
Aducci
  • 26,101
  • 8
  • 63
  • 67
  • I thought this would work, but it doesn't. I'm not entirely sure, but I think that by only changing this line it will yield a "Not Contains" result for each iteration through the chunked values. – David Murdoch Aug 10 '11 at 19:32
  • Let me clarify that comment: it will yield a DUPLICATE result for each iteration. – David Murdoch Aug 10 '11 at 20:18
-1

Not sure if this is a possible solution but how about something like:

public static IEnumerable<T> NotInRange<T,TValue)(this IQueryable<T> source, Expression<Func<T,TValue>> selector, int blockSize, IEnumerable<TValue> values) 
{ 
     return !source.InRange(selector, blockSize, values); 
}
dreza
  • 3,605
  • 7
  • 44
  • 55
  • `InRange` doesn't return `boolean` so `NotInRange` shouldn't either. Also, the code you provided isn't valid and contains some typos. I admit that the term `InRange` implies a boolean and the author probably should have named it something like `WhereInRange`. – David Murdoch Aug 09 '11 at 20:48