Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ many-to-many intersection

I'm trying to query Posts based on a list of Tags:

public class Post
{
  public int? Id {get;set;}
  public string Name {get;set;}
  public virtual ICollection<Tag> Tags {get;set;}
}
public class Tag
{
  public int? Id {get;set;}
  public string Name {get;set;}
  public vritual ICollection<Post> Posts {get;set;}
}

Now I want to return posts based a list of tags: IList<Tag> searchTags = ParseTagsFromSearchString("tag1,tag2,tag3"); // this function checks the tags in the database, so all the primary keys are available in the list

When a post contains one or more tags that also exists in searchTags it should be included in the result. I have tried the following:

var q = from s in Context.Registrations
                    where s.Tags.Intersect(tagList)
                    select s;

Error: Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<Models.Tag>' to 'bool'

var q = from s in Context.Registrations
                    where s.Tags.Any(t => tagList.Any(t2 => t.Id.Value == t2.Id.Value))
                    select s;

Runtime error: NotSupportedException: Unable to create a constant value of type 'Models.Tag'. Only primitive types ('such as Int32, String, and Guid') are supported in this context. Any ideas?

-- update Jan. 4: The answers point to the right solution, but in my code I still have the NotSupportedException. Is it possible the nullable integer causes this since it is not a primitive type?

like image 942
Marthijn Avatar asked Jan 03 '12 14:01

Marthijn


1 Answers

You're almost there, just change the Intersect(taglist) to Intersect(taglist).Any()

Here's a working example (going along your definitions for Post and Tag):

Tag tag1 = new Tag() { Id = 1, Name = "tag1" };
Tag tag2 = new Tag() { Id = 2, Name = "tag2" };
Tag tag3 = new Tag() { Id = 3, Name = "tag3" };

List<Post> posts = new List<Post>() {
    new Post() { Id = 1, Name = "post1", Tags = new Tag[] {tag1} },
    new Post() { Id = 2, Name = "post2", Tags = new Tag[] {tag2} },
    new Post() { Id = 3, Name = "post3", Tags = new Tag[] {tag3} },
    new Post() { Id = 4, Name = "post13", Tags = new Tag[] {tag1, tag3} },
};

List<Tag> searchTags = new List<Tag>() { tag1, tag2 };

IEnumerable<Post> matching = posts.Where(p => p.Tags.Intersect(searchTags).Any());
//matching now contains post1, post2 and post13

now, in real code you probably won't use the same instances for tags in the search list and the Posts, so you'll have to override Equals and GetHashCode for Tag or provide an IEqualityComparer in the call to Intersect

like image 96
voidengine Avatar answered Oct 19 '22 02:10

voidengine