views:

105

answers:

2

I really like PredicateBuilder. It allows me to build all sorts of queries very dynamically. The predicate variable can be passed around to different objects and they can add onto it with values they know about, etc. Except when I am needing to use a .Contains on a hashed collection. Bzzt! Crash and burn.

For instance (example/pseudo code, this may or may not compile/run):

protected Expression<Func<MyClass, bool>> GetWherePredicate()
{
    string[] selectedValues = Request.Form.GetValues("checkbox1") ?? new string[0];
    HashSet<int> selectedIDs = new HashSet<int>(selectedValues.Cast<int>());

    Expression<Func<MyClass, bool>> predicate = PredicateBuilder.True<MyClass>();
    predicate = predicate.And(s => selectedIDs.Contains(s.ID));

    return predicate;
}

protected void Retrieve()
{
    Expression<Func<MyClass, bool>> predicate = GetWherePredicate();
    IEnumerable<MyClass> retrievedValues = MyDataContext.GetTable<MyClass>.Where(predicate);
}

When I try to do that, I get a NotSupportedException: Method 'Boolean Contains(Int32)' has no supported translation to SQL due to the selectedIDs HashSet not being in scope. If I do this all in the same method, then it works fine.

I need to know the right way to get my predicate there to resolve or compile or whatever so that it can be used in a different scope from where the HashSet is declared. Any help?

UPDATE: I had this pretty wrong. The code below works fine, so there is no scope conflict. Thanks Jay.

string[] selectedValues = Request.Form.GetValues("checkbox1") ?? new string[0];
Expression<Func<MyClass, bool>> predicate = PredicateBuilder.True<MyClass>();
predicate = predicate.And(s => selectedValues.Contains(s.ID.ToString()));
+2  A: 

From the exception you cite, it seems unlikely that scope is a factor here.

My answer to this is that you need to declare selectedIDs as IEnumerable<int> instead of HashSet<int> (or just cast it before calling Contains(), but that doesn't account for it working when all in the same method, so I'm unsure.

Without seeing any actual code that is exhibiting this behaviour, it will be difficult to troubleshoot any further.

Jay
Ok, I'll work my code over a bit and see if I'm going down the wrong path here. Thanks.
sliderhouserules
A: 

Do not get razzle-dazzled by the name Contains... there are many methods that are named that, and not many have supported translations into SQL.

  • Enumerable.Contains<T> and List<T>.Contains are methods with supported translations.
  • HashSet<T>.Contains is a method that has no supported translation.

There are many resolutions, but I recommend:

IEnumerable<string> selectedValueQuery =
  Request.Form.GetValues("checkbox1") ?? Enumerable.Empty<string>();
List<string> selectedIds = selectedValueQuery
  .Cast<int>()
  .Distinct()
  .ToList();

Although a simpler solution might be:

IEnumerable<int> selectedIDs = new HashSet<int>(selectedValues.Cast<int>()); 

Edit: It's not about concrete implementation of Contains at all. It's about whether the linqtosql query provider can identify the method and translate it into an IN (list) sql expression. The recognition code looks at the type of the parameter used in the expression. The recognition code does not use polymorphism/implementation nor does it walk the inheritence tree looking for other possibilities.

List<int> myList = new List<int>(){1, 2, 3};
IList<int> myIList = myList;
IEnumerable<int> myIEnumerable = myList;

  //works by List<T>.Contains()
db.Customers.Where(c => myList.Contains(c.CustomerID));

  //doesn't work, no translation for IList<T>.Contains
db.Customers.Where(c => myIList.Contains(c.CustomerID));

  //works by Enumerable.Contains<T>()
db.Customers.Where(c => myIEnumerable.Contains(c.CustomerID));

  //works by Enumerable.Contains<T>()
db.Customers.Where(c => Enumerable.Contains(myIEnumerable, c.CustomerID));

Even though these parameters reference the same instance, different translation behaviors occur because the type of the parameter is different.

.Contains() is not called as it is translated, therefore its implementation is irrelevant. It could throw NotImplementedException or return true;

David B
Sweet, thanks. I spent some time reading about the difference between IEnumerable<>.Contains and IList<>.Contains and I think that may be at the heart of my issue at first. So your recommended types here are good guides.
sliderhouserules
IList<T>.Contains has no supported translation. List<T>.Contains does. There is no such thing as IEnumerable<T>.Contains
David B
It was a casual comment and I wrote the names by memory. I wasn't trying to document things for people. Sometimes this site annoys me as much as anything... /sighhttp://msdn.microsoft.com/en-us/library/bb352880.aspx
sliderhouserules
I normally wouldn't be so prickly on the class.methods names, however laxness on this is what caused the original problem.
David B
I don't think it was laxness at all. It was lack of knowledge. Now I know IEnumerable<T> has the Enumerable.Contains extension method which works with LINQ. HashSet<T> implements IList<T> which implements ICollection<T> which has a Contains method that doesn't work with LINQ. So technically you were as incorrect as you were saying I was, which is rather ironic don't you think?
sliderhouserules
Incidentally, List<T> implements ICollection<T>.Contains so it likely won't work either. I haven't tested it, but thought I'd put that out there. msdn.microsoft.com/en-us/library/bhkz42b3.aspx
sliderhouserules
List<T> works. List<T>.Contains does implement ICollection<T>.Contains (as you say). The problem is that translation is done by the VARIABLE type, not the instance type. It is not polymorphic. Under this view, List<T>.Contains and ICollection<T>.Contains and IList<T>.Contains are all different methods.
David B
So List<T> has a concrete implementation that works; Enumerable.Contains is an extension method with a concrete implementation that works; HashSet<T> has a concrete implementation that does not work; so it doesn't matter what interface they implement, it matters where the concrete implementation lives, and whether that implementation has a translation to SQL for LINQ?
sliderhouserules