views:

7355

answers:

7

So, I am using the Linq entity framework. I have to entities "Content" and "Tag" They are in a many-to-many relationship with one another. Content can have many tags and Tags can have many Contents. So I am trying to write a query to select all contents where any tags names are equal to "blah."

The entities both have a collection of the other entity as a property(but no IDs). This is where I am struggling. I do have a custom expression for "Contains" (so, whoever may help me, you can assume that I can do a "contains" for a collection). I got this expression from: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2670710&SiteID=1

Edit 1

I ended up finding my own answer.

A: 
tags.Select(testTag => testTag.Name)

Where does the tags variable gets initialized from? What is it?

chakrit
Oh, sorry.. That is passed as a plain IList<Tag>.
Phobis
+1  A: 

This is what the question itself asks for:

contentQuery.Where(
    content => content.Tags.Any(tag => tag.Name == "blah")
);

I'm not sure what the thought process was to get to the questioner's code, really, and I'm not entirely sure exactly what its really doing. The one thing I'm really sure of is that .AsQueryable() call is completely unnecessary -- either .Tags is already an IQueryable, or the .AsQueryable() is just going to fake it for you -- adding extra calls in where there doesn't need to be any.

Alex Lyman
+2  A: 

Summing it up...

contentQuery.Where(
    content => content.Tags.Any(tag => tags.Any(t => t.Name == tag.Name))
);

So is that what you're expecting?

I'm a little confused.

chakrit
Yes... this is along the lines of what I need... I am now getting an error: > Unable to create a constant value of type 'Closure type'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.It is coming from: .Any(t => t.Name == tag.Name)
Phobis
I get the same error, Phobis.
Victor Rodrigues
A: 
 contentQuery.Where(
    content => content.Tags.Any(tag => tags.Any(t => t.Name == tag.Name))
);

Yes... this is along the lines of what I need... the only issue with this is that I am getting an error:

Unable to create a constant value of type 'Closure type'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.

It is coming from:

.Any(t => t.Name == tag.Name)

Any Ideas?

Phobis
A: 

NOTE: please edit the question itself, rather than replying with an answer -- this is not a discussion thread, and they can re-order themselves at any time

If you're searching for all Contents that are marked with any one of a set of tags:

IEnumerable<Tag> otherTags;
...
var query = from content in contentQuery
            where content.Tags.Intersection(otherTags).Any()
            select content;

It looks like you might be using LINQ To SQL, in which case it might be better if you write a stored procedure to do this one: using LINQ to do this will probably not run on SQL Server -- it's very likely it will try to pull down everything from contentQuery and fetch all the .Tags collections. I'd have to actually set up a server to check that, though.

Alex Lyman
I am not useing LINQ to SQL, it is LINQ to Entities.
Phobis
If I do this, I get error: Unable to create a constant value of type 'Closure type'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
Phobis
+1  A: 

The error is related to the 'tags' variable. LINQ to Entities does not support a parameter that is a collection of values. Simply calling tags.AsQueryable() -- as suggested in an ealier answer -- will not work either because the default in-memory LINQ query provider is not compatible with LINQ to Entities (or other relational providers).

As a workaround, you can manually build up the filter using the expression API (see this forum post) and apply it as follows:

var filter = BuildContainsExpression<Element, string>(e => e.Name, tags.Select(t => t.Name));
var query = source.Where(e => e.NestedValues.Any(filter));
This sort of helped... but I still didn't get a working solution from this :(
Phobis
+6  A: 

After reading about the PredicateBuilder, reading all of the wonderful posts that people sent to me, posting on other sites, and then reading more on Combining Predicates and Canonical Function Mapping.. oh and I picked up a bit from Calling functions in LINQ queries (some of these classes were taken from these pages).

I FINALLY have a solution!!! Though there is a piece that is a bit hacked...

Let's get the hacked piece over with :(

I had to use reflector and copy the ExpressionVisitor class that is marked as internal. I then had to make some minor changes to it, to get it to work. I had to create two exceptions (because it was newing internal exceptions. I also had to change the ReadOnlyCollection() method's return from:

return sequence.ToReadOnlyCollection<Expression>();

To:

return sequence.AsReadOnly();

I would post the class, but it is quite large and I don't want to clutter this post any more than it's already going to be. I hope that in the future that class can be removed from my library and that Microsoft will make it public. Moving on...

I added a ParameterRebinder class:

public class ParameterRebinder : ExpressionVisitor {
     private readonly Dictionary<ParameterExpression, ParameterExpression> map;

     public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map) {
      this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
     }

     public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp) {
      return new ParameterRebinder(map).Visit(exp);
     }

     internal override Expression VisitParameter(ParameterExpression p) {
      ParameterExpression replacement;
      if (map.TryGetValue(p, out replacement)) {
       p = replacement;
      }
      return base.VisitParameter(p);
     }
    }

Then I added a ExpressionExtensions class:

public static class ExpressionExtensions {
     public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge) {
      // build parameter map (from parameters of second to parameters of first)
      var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);

      // replace parameters in the second lambda expression with parameters from the first
      var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

      // apply composition of lambda expression bodies to parameters from the first expression 
      return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
     }

     public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
      return first.Compose(second, Expression.And);
     }

     public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
      return first.Compose(second, Expression.Or);
     }
    }

And the last class I added was PredicateBuilder:

public static class PredicateBuilder {
 public static Expression<Func<T, bool>> True<T>() { return f => true; }
 public static Expression<Func<T, bool>> False<T>() { return f => false; }

}

This is my result... I was able to execute this code and get back the resulting "content" entities that have matching "tag" entities from the tags that I was searching for!

 public static IList<Content> GetAllContentByTags(IList<Tag> tags) {
  IQueryable<Content> contentQuery = ...

  Expression<Func<Content, bool>> predicate = PredicateBuilder.False<Content>();

  foreach (Tag individualTag in tags) {
   Tag tagParameter = individualTag;
   predicate = predicate.Or(p => p.Tags.Any(tag => tag.Name.Equals(tagParameter.Name)));
  }

  IQueryable<Content> resultExpressions = contentQuery.Where(predicate);

  return resultExpressions.ToList();
 }

Please let me know if anyone needs help with this same thing, if you would like me to send you files for this, or just need more info.

Phobis
You can use pastebin.com or gist.github.com to paste your hacked version of ExpressionVisitor, the class might be useful to some :)
chakrit